一、线程私有数据
进程内的所有线程共享进程的数据空间,所以全局变量为所有线程共有。在某些场景下,线程需要保存自己的私有数据,这时可以创建线程私有数据(Thread-specific Data)TSD来解决。在线程内部,私有数据可以被线程的各个接口访问,但对其他线程屏蔽
int pthread_key_create(pthread_key_t *key, void (*destr_function) (void*));//创建键
参数:key:存放申请到的键值
destr_function:线程执行完毕退出时,将key指向的值作为入参,进行调用
int pthread_setspecific(pthread_key_t key, const void *pointer);//为指定键值设置线程私有数据
void * pthread_getspecific(pthread_key_t key);//从指定键读取线程的私有数据
void * pthread_getspecific(pthread_key_t key);//删除一个键,此时并不删除数据
二、线程的取消点
线程还有两个属性没有包含在线程属性中,那就是线程的可取消状态和可取消类型。前面说过,pthread_cancel()函数只是请求指定线程退出,但是该线程是否会立即退出,就取决于它的可取消状态和可取消类型或者是否到达线程的可取消点(可取消点是指程序运行到含有取消点的系统调用)。
int pthread_setcancelstate(int newstate,int* oldstate);
作用:设置线程的可取消状态为newstate,并将之前的状态把保存在oldstate中。
取值有以下两种:PTHREAD_CANCEL_ENABLE,PTHREAD_CANCEL_DISABLE
还有一种情况是,当我们的请求成功发送,但是因为该线程一直处于忙碌状态,始终进入不到含有取消点的系统调用中,这时候我们可以通过以下函数,主动设置一个可取消点。
void pthread_testcancel(void)
int pthread_setcanceltype(int type,int* oldtype);//设置线程的可取消类型
type取值有一下两种情况:PTHREAD_CANCEL_ASYNCHRONOUS(异步取消)、PTHREAD_CANCEL_DEFERRED(延迟取消)
异步取消可以在任意时间响应。
三、多线程与信号
在多线程环境下,产生的信号是传递给整个进程的,一般而言,所有线程都有机会收到这个信号。
如果是异常产生的信号(比如程序错误,像SIGPIPE、SIGEGV这些),则只有产生异常的线程收到并处理。
如果是外部使用kill命令产生的信号,通常是SIGINT、SIGHUP等job control信号,则会遍历所有线程,直到找到一个不阻塞该信号的线程,然后调用它来处理。(一般从主线程找起),注意只有一个线程能收到。
默认情况下,信号将由主进程接收处理,就算信号处理函数是由子线程注册的,除非主线程屏蔽该信号,每个线程均有自己的信号屏蔽集(信号掩码),可以使用pthread_sigmask函数来屏蔽某个线程对某些信号的响应处理,仅留下需要处理该信号的线程来处理指定的信号。实现方式是:利用线程信号屏蔽集的继承关系(在主进程中对sigmask进行设置后,主进程创建出来的线程将继承主进程的掩码)。
在多线程代码中,总是使用sigwait或者sigwaitinfo或者sigtimedwait等函数来处理信号。而不是signal或者sigaction等函数。因为在一个线程中调用signal或者sigaction等函数会改变所有线程中的信号处理函数。而不是仅仅改变调用signal/sigaction的那个线程的信号处理函数。
int pthread_sigwait(const sigset_t *set, int *sig);//线程同步的等待信号的到来
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);//设置线程的信号屏蔽集
在多线程程序中,一个线程可以使用pthread_kill对同一个进程中指定的线程(包括自己)发送信号。注意在多线程中 一般不使用kill函数发送信号,因为kill是对进程发送信号,结果是:正在运行的线程会处理该信号,如果该线程没有注册信号处理函数,那么会导致整个进程退出。
int pthread_kill(pthread_t thread, int sig);//发送信号给指定线程
四、fork()与多线程
在多线程运行的情况下调用fork()函数,仅会将发起调用的线程拷贝到子进程中。但全局变量的状态以及全部的pthreads对象(如相互排斥量、条件变量等)都会在子进程中得以保留,这就造成一个危急的局面。比如:一个线程在fork()被调用前锁定了某个相互排斥量,且对某个全局变量的更新也做到了一半,此时fork()被调用,全部数据及状态被复制到子进程中,那么子进程中对该相互排斥量就无法解锁(由于其并不是该相互排斥量的属主),假设再试图锁定该相互排斥量就会导致死锁。所以fork()之后,子进程不能调用:
(1)malloc:由于malloc()在訪问全局状态时会加锁。
(2)不论什么可能分配或释放内存的函数,包含new、map::insert()、snprintf() ……
(3)不论什么pthreads函数。你不能用pthread_cond_signal()去通知父进程,仅仅能通过读写pipe(2)来同步。
(4)printf()系列函数,由于其它线程可能恰好持有stdout/stderr的锁。
除了man 7 signal中明白列出的“signal安全”函数之外的不论什么函数。
因此,推荐在多线程程序中调用fork()的唯一情况是:其后马上调用exec()函数运行,彻底隔断子进程与父进程的关系。由新的进程覆盖掉原有的内存,使得子进程中的全部pthreads对象消失。
int pthread_atfork (void (*prepare) (void), void (*parent) (void), void (*child) (void));
参数:prepare: 新进程产生之前被调用
parent: 新进程产生之后在父进程被调用
child: 新进程产生之后,在子进程被调用