一、线程属性
二、同步属性
三、重入
四、线程特定数据
线程特定数据(thread-specific data),也称为线程私有数据(thread-private data),是存储和查询某个特定线程相关数据的一种机制。
在分配线程特定数据之前,需要创建与该数据关联的键。这个键用于获取对线程特定数据的访问:
#include <pthread.h>
int pthread_key_create(pthread_key_t* keyp, void (*destructor)(void*));
创建的键存储在keyp指向的内存单元中,这个键可以被进程中所有线程使用,但每个线程把这个键与不同的线程特定数据地址进行关联。
函数还可为该键关联一个可选择的析构函数,当这个线程退出时,如果数据地址已经被置为非空值,那么析构函数就会被调用,它唯一的参数就是该数据地址;如果地址为空,则表明无析构函数。
线程可以为线程特定数据分配多个键,每个键都可以有一个析构函数与它关联。
对所有线程,可以通过调用pthread_key_delete来取消键与线程特定数据值之间的关联关系:
#include <pthread.h>
int pthread_key_delete(pthread_key_t key);
调用pthread_key_delete并不会激活与键关联的析构函数,所以需要手动释放。
为了确保分配的键不会由于在初始化阶段的竞争而产生变动,使用pthread_once:
#include <pthread.h>
pthread_once_t initflag = PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t* initflag, void(*initfn)(void));
init必须是全局变量或静态变量,而且必须初始化为PTHREAD_ONCE_INIT。若每个线程都调用pthread_once,系统就能保证initfn只会被调用一次。在initfn中进行键的创建动作即可。
键创建后,可以把键和线程特定数据关联起来,可以获取特定数据地址
#include <pthread.h>
void* pthread_getspecific(pthread_key_t key);
//返回线程特定数据地址;若没有值与键关联,则返回NULL
int pthread_setspecific(pthread_key_t key, const void* value);
//成功,返回0;出错,返回错误编号
五、取消选项
六、线程与信号
每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的。
进程中的信号时递送到单个线程的。若信号与硬件故障相关,则该信号一般会发送到引起该事件的线程中去,其他信号则被发送到任意一个线程。
进程可以用sigpromask来阻止信号发送,线程中必须使用:
#include <signal.h>
int pthread_sigmask(int how, const sigset_t* set, sigset_t* oset);
//成功返回0,出错返回错误码
how参数可取SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK。参数意义分别是向信号屏蔽字添加、移除、替换信号集。
线程等待一个或多个信号的出现:
#include <signal.h>
int sigwait(const sigset_t* set, int* signop)
set指定要等待的信号集,返回时,signop指向发送信号的数量。
要把信号发送给进程,可以调用kill。要把信号发送给线程:
int pthread_kill(pthread_t thread, int signo);
可以通过传一个0值的signo来检查线程是否存在。
注:闹钟定时器是进程资源,并且所有线程共享相同的闹钟。
七、线程和fork
子进程通过继承整个地址空间的副本,继承了每个互斥量、读写锁和条件变量的状态。在子进程内部,只存在一个线程,它是由父进程调用fork的线程的副本构成的。如果父进程的线程中占有锁,子进程同样占有这些锁。问题是子进程并不包含占有锁的线程的副本,所以子进程没有办法知道它占有了那些锁、需要释放哪些锁。
若子进程从fork返回后立马调用其中一个exec函数,旧的地址空间就被丢弃,所以锁的状态就无关紧要。但如果子进程需要继续处理工作的话,还需要采取其他策略:
清除锁状态
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));
- prepare:父进程在fork创建子进程之前调用。目的是获取父进程定义的所有锁
- parent:fork创建子进程之后、返回之前,在父进程上下文中调用。目的是对prepare获取的所有锁进行解锁
- child:fork返回之前,在子进程的上下文中调用。目的也是释放prepare获取的所有锁
八、线程和I/O
pread(原子操作) 和 pwrite解决对文件描述符并发读写问题。