系统api-线程控制

线程控制

线程限制

PTHREAD_DESTRUCTOR_ITERATIONS	线程退出系统时试图销毁线程特定数据最大次数	_SC_THREAD_DESTRUCTOR_ITERATIONS
PTHREAD_KEYS_MAX				进程可创建键的最大次数						_SC_THREAD_KEYS_MAX
PTHREAD_STACK_MIN				一个线程的栈可用的最小字节数				_SC_THREAD_STACK_MIN
PTHREAD_THREADS_MAX				进程可创建的最大线程数						_SC_THREAD_THREADS_MAX

线程属性

(1). 每个对象与它自己类型的属性对象进行关联(线程与线程属性关联,…)。属性对象对应用程序不透明,有相应的函数来管理这些属性对象。
(2). 有一个初始化函数,把属性设置为默认值。
(3). 有一个销毁属性对象的函数。如初始化函数分配了与属性对象关联的资源,销毁函数负责释放这些资源。
(4). 每个属性有一个从属性对象中获取属性值的函数。成功,返回0。失败,返回错误编号。
(5). 每个属性有一个设置属性值的函数。属性值按值传递。

detachstate,线程的分离状态属性.

int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
int pthread_attr_getdetachstate(const pthread_attr_t *restrict attr, int* detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int *detachstate);

如果对现有的某个线程的终止状态不感兴趣的话,可使用pthread_detach让操作系统在线程退出时收回它所占用的资源。如在创建线程时不需要了解线程终止状态,可修改pthread_attr_tdetachstate属性,让线程一开始就处于分离状态。

可用pthread_attr_setdetachstate,把线程属性detachstate设为PTHREAD_CREATE_DETACHED,以分离启动线程。或PTHREAD_CREATE_JOINABLE正常启动线程,应用程序可获取线程终止状态。

guardsize,线程栈末尾的警戒缓存区大小(字节数).
stackaddr,线程栈的最低地址.
stacksize,线程栈的最小长度(字节数).
对遵循POSIX标准的系统,不一定要支持线程栈属性。对遵循XSI选项的系统,需要支持。可在编译阶段,用_POSIX_THREAD_ATTR_STACKADDR_POSIX_THREAD_ATTR_STACKSIZE来检查系统是否支持每个线程栈属性。也可在运行阶段把_SC_THREAD_ATTR_STACKADDR_SC_THREAD_ATTR_STACKSIZE传给sysconf

可用pthread_attr_getstackpthread_attr_setstack对线程栈属性进行管理。

int pthread_attr_getstack(const pthread_attr_t *restrict attr, 
	void **restrict stackaddr, size_t *restrict stacksize);
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);
int pthread_attr_getstacksize(const pthread_attr_t *restrict attr, size_t *restrict stacksize);
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

线程属性guardsize控制着线程栈末尾后,用以避免栈溢出的扩展内存的大小。
属性默认值由具体实现定义。常用值是系统页大小。如果修改了线程属性stackaddr,系统就认为我们将自己管理栈,使警戒缓冲区机制无效。等同于把guardsize设为0

int pthread_attr_getguardsize(const pthread_attr_t *restrict attr, size_t *restrict guardsize);
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);

如果线程的栈指针溢出到警戒区域,应用程序可能通过信号接收出错信息。

同步属性

(1). 互斥量属性
互斥量属性用pthread_mutexattr_t结构表示。

int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);

值得注意的3个属性是:进程共享属性,健壮属性,类型属性。
PTHREAD_PROCESS_SHARED,从多进程共享的内存块中分配的互斥量可用于共享进程间的同步。

int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);

互斥量健壮属性与在多个进程间共享的互斥量有关。持有互斥量进程终止时,需要解决互斥量状态恢复问题。

int pthread_mutexattr_getrobust(const pthread_mutexattr_t *restrict attr, int *restrict robust);
int pthread_mutexattr_setrobust(pthread_mutexattr_t *attr, int robust);

健壮属性默认值为PTHREAD_MUTEX_STALLED,意味着持有互斥量的进程终止时不需要采取特别的动作。
这时,使用互斥量后行为未定义。等待该互斥量的应用会被拖住。
另一个取值是PTHREAD_MUTEX_ROBUST,将导致后续其他等待此锁线程阻塞。
使用健壮的互斥量,需检查pthread_mutex_lock3个返回值:
不需要恢复的成功,需要恢复的成功,失败。

如应用状态无法恢复,线程对互斥量解锁后,互斥量永久不可用。为避免,可调用pthread_mutex_consistent,指明与该互斥量相关的状态在互斥量解锁前是一致的。

int pthread_mutex_consistent(pthread_mutex_t *mutex);

如线程没有先调用pthread_mutex_consistent就对互斥量解锁,则其他试图获取该互斥量线程得到错误码ENOTRECOVERABLE

类型互斥量属性控制着互斥量的锁定特性。
POSIX.1定义了4中类型。
PTHREAD_MUTEX_NORMAL,不做特殊的错误检查或死锁检测。
PTHREAD_MUTEX_ERRORCHECK,提供错误检查
PTHREAD_MUTEX_RECURSIVE,允许同一线程在互斥量解锁前对该互斥量多次加锁。递归互斥量维护锁的计数。在解锁次数和加锁次数不相同下,不会释放锁。
PTHREAD_MUTEX_DEFAULT,由系统将其映射为上述三者的一种

互斥量类型没解锁时重新加锁不占有时解锁在已解锁时解锁
PTHREAD_MUTEX_NORMAL死锁未定义未定义
PTHREAD_MUTEX_ERRORCHECK返回错误返回错误返回错误
PTHREAD_MUTEX_RECURSIVE允许返回错误返回错误
PTHREAD_MUTEX_DEFAULT未定义未定义未定义
int pthread_mutexattr_gettype(const pthread_mutexattr_t* restrict attr, int *restrict type);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);

(2). 读写锁属性
pthread_rwlockattr_init初始化pthread_rwlockattr_t结构。用pthread_rwlockattr_destroy销毁。

int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t* restrict attr, int *restrict pshared);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);

(3). 条件变量属性
进程共享属性,时钟属性

int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_destroy(pthread_condattr_t *attr);
int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr, int *restrict pshared);
int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared);
int pthread_condattr_getclock(const pthread_condattr_t *restrict attr, clockid_t *restrict clock_id);
int pthread_condattr_setclock(pthread_condattr_t *attr, clockid_t clock_id);

重入

多个控制线程在相同的时间有可能调用相同的函数。如果一个函数在相同的时间点可被多个线程安全地调用,称该函数是线程安全的。支持线程安全函数的系统会在<unist.h>中定义符号_POSIX_THREAD_SAFE_FUNCTIONS。应用也可在sysconf中传入_SC_THREAD_SAFE_FUNCTIONS
在这里插入图片描述
在这里插入图片描述

如一个函数对多个线程来说是可重入的,称其是线程安全的。如果函数对异步信号处理程序的是安全的,就说函数是异步信号安全的。以线程安全方式管理FILE对象。可用flockfile,ftrylockfile获取给定FILE对象关联的锁。此锁是递归的。

int ftrylockfile(FILE* fp);
void flockfile(FILE* fp);
void funlockfile(FILE* fp);

int getchar_unlocked(void);
int getc_unlocked(FILE* fp);
// 成功,返回c。出错,返回EOF。
int putchar_unlocked(int c);
int putc_unlocked(int c, FILE *fp);

线程特定数据

在分配线程特定数据前,需要创建与该数据关联的键。这个键将用于获取对线程特定数据的访问。

int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void*));

创建的键存储在keyp指向的内存单元中,可被进程的所有线程使用。但每个线程把这个键与不同线程特定数据地址关联。
创建新键时,每个线程的数据地址为空值。线程退出时,如数据地址已被置为非空值,则析构函数会被调用,唯一参数为数据地址。
线程调pthread_exit或线程返回,正常退出,析构函数被调用。
线程取消时,在最后的清理处理程序返回后,析构函数被调用。
如线程调了exit, _exit, _Exitabort或其他非正常退出,不调用析构。
线程退出时,线程特定数据的析构函数按系统中定义的顺序调用。
所有析构函数完成后,检查是否还有非空的线程特定数据值与键关联,如有,再次析构。可通过pthread_key_delete取消键和线程特定数据值间的关系。

int pthread_key_delete(pthread_key_t key);
pthread_once_t initflag = PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *initflag,// 需指向一个全局或静态变量,初始化为PTHREAD_ONCE_INIT
	void (*initfn)(void));
void *pthread_getspecific(pthread_key_t key);
int pthread_setspecific(pthread_key_t key, const void* value);

在多线程环境中,有些事仅需要执行一次。通常当初始化应用程序时,可以比较容易地将其放在main函数中。
但当你写一个库时,就不能在main里面初始化了,你可以用静态初始化,但使用一次初始化(pthread_once)会比较容易些。

int pthread_once(pthread_once_t *once_control, void (*init_routine) (void))

功能:
本函数使用初值为PTHREAD_ONCE_INITonce_control变量,保证init_routine()函数在本进程执行序列中仅执行一次。
在多线程编程环境下,尽管pthread_once()调用会出现在多个线程中,init_routine()函数仅执行一次,究竟在哪个线程中执行是不定的,是由内核调度来决定。

取消选项

可取消状态,可取消类型。可取消状态属性可以是PTHREAD_CANCEL_ENABLE,也可是PTHREAD_CANCEL_DISABLE

int pthread_setcancelstate(int state, int *oldstate);

pthread_cancel不等待线程终止。线程在取消请求发出后,还是继续运行,直到到达某个取消点。
在这里插入图片描述

线程启动时默认可取消状态为PTHREAD_CANCEL_ENABLE。线程在到达取消点,检查是否有取消请求并处理。

void pthread_testcancel(void);

在这里插入图片描述

int pthread_setcanceltype(int type, int* oldtype);

线程和信号

每个线程都有自己的信号屏蔽字。但信号的处理所有线程共享。
进程中的信号被递送到单个线程。有些信号被发送到任意一个线程。
sigprocmask的行为在多线程的进程中没定义。此时,线程用pthread_sigmask

int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
int sigwait(const sigset_t *restrict set,// 等待的信号集
	int *restrict signop);// 信号数量
int pthread_kill(pthread_t thread, int signo);

闹钟是进程资源。

线程和fork

线程调用fork时,子进程通过继承整个地址空间的副本。
从父进程继承了每个互斥量,读写锁,条件变量。
如果父进程包含一个以上线程,子进程fork后,如果不是马上调exec,就需要清理锁的状态。
在子进程内部,只存在一个线程,由父进程中调用fork的线程副本构成。
父进程中的线程如占有锁,子进程将同样占有这些锁。但子进程不包含占有锁的线程的副本。故,子进程不知道占有了哪些锁,释放哪些锁。在多线程的进程中,为避免不一致状态,
POSIX.1声明,fork返回和子进程调用其中一个exec间,子进程只能调用异步信号安全的函数。要清除锁状态,可以用pthread_atfork建立fork

int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));

最多可安装3个帮助清理锁的函数。prepare在由父进程在fork创建子进程前调用,用于获取父进程定义的所有锁。parentfork创建子进程后,返回前,在父进程上下文调用,用于对prepare获取的所有锁进行解锁。childfork返回前在子进程上下文调用。用于对prepare获取的所有锁进行解锁。

好像出现了下列事件序列:
父进程获取所有的锁,子进程获取所有的锁,父进程释放它的锁,子进程释放它的锁。

可多次调用pthread_atfork来设置多套fork处理程序。使用了多个fork处理程序时,parent,child处理程序按注册顺序调用。prepare按注册时相反顺序调用。
如:模块A调模块B的函数,且每个模块有自己一套锁。如锁的层次是,AB之前,模块B需在模块A之前设置它的fork处理程序。父进程调用fork时会执行以下步骤,假设子进程在父进程之前运行。
a. 调模块Aprepare
b. 调模块Bprepare
c. 创建子进程
d. 调模块Bchild
e. 调模块Achild
f. fork返回到子进程
g. 调模块Bparent
h. 调模块Aparent
i. fork返回父进程

线程和I/O

preadpwrite可以使设置文件位置和读取或写入整合为原子操作,适合多线程。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

raindayinrain

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值