注:函数未经说明全部定义在<pthread.h>
1.分离状态
在任何一个时间点上,线程是可结合的(joinable),或 者是分离的(detached)。一个可结合的线程能够被其他线程收回其资源和杀死;在被其他线程回收之前,它的存储器资源(如栈)是不释放的。相反,一 个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。
线程的分离状态决定一个线程以什么样的方式来终止自己。在默认情况下线程是非分离状态的,这种情况下,原有的线程等待创建的线程结束。只有当 pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运 行结束了,线程也就终止了,马上释放系统资源。程序员应该根据自己的需要,选择适当的分离状态。所以如果我们在创建线程时就知道不需要了解线程的终止状 态,则可以pthread_attr_t结构中的detachstate线程属性,让线程以分离状态启动。
设置线程分离状态的函数为 pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程)。这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在 pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用(有pthread_join的不会这样,因为他要进行线程的资源回收),这样调用pthread_create的 线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用 pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。设置一段等待时间,是 在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。
2线程属性
定义:每个对象都有一个对应的属性对象,比如说线程对应这线程属性对象,互斥量对应着互斥量属性对象
在每一个属性对象中,我们是不知道属性对象中的结构的,这样有利于增强数据的可移植性
2.1线程属性初始化销毁
(1)pthread_attr_init(pthread_attr_t* attr);
对一个线程属性进行初始化,初始化后的属性是系统默认属性
(2)pthread_attr_destroy(pthread_attr_t* attr);
对一个分配了的属性进行销毁
2.2线程属性如何设置
detachstate 线程的分离状态属性;guardsize 线程栈末尾的警戒缓冲区大小(字节数);stackaddr 线程栈的最低地址;stacksize 线程栈的最小长度
2.2.1关于分离属性的设置以及产看
PTHREAD_CREATE_DETACKED 分离方式释放
PTHREAD_CREATE_JOINABLE 正常方式启动线程
(1)int pthread_attr_getdetackstate(const pthread_attr_t *restrict attr,int *detackstate);
获得启动方式
(2)int pthread_attr_setdetackstate(const pthread_attr_t *restrict attr,int *detachstate);
设置启动方式,是以什么方式进行启动的
2.2.2线程栈属性
对线程的栈属性进行设置的时候,应该首先对这两个属性进行检查,看一下操作系统是否支持此属性
用sysconf函数进行验证 _SC_THREAD_ATTR_STACKADDR,_SC_THREAD_ATTR_STACKSIZE
(1)int pthread_attr_setstack(pthread_attr_t *attr,void *stackaddr,size_t stacksize);
首先我们要进行明确,线程栈是使用的进程栈,每个线程栈在默认的时候分配的是相同大小,但是如此分配会出现,分配不均匀以及其他的一些问题,所以应该进行设置
attr指的是线程属性
stackaddr指的是可以用作线程栈地址的最低可寻址地址,但是折不一定就是一个栈的开始为止,要看栈是向上还是向下增长的
stacksize指的是栈的大小
记住,stackaddr和stacksize所确定的范围不能与原来分配的地址范围进行重叠,注意分配空间应该用malloc进行分配
(2)int pthread_attr_getstack(const pthread_attr_t *restrict attr,void **restrict stackaddr,size_t *restrict stacksize);
得到栈的地址,得到栈的大小
(3)int pthread_attr_getstacksize(const pthread_attr_t *restrict attr,size_t *restrict stacksize);
得到栈的大小
(4)int pthread_attr_setstacksize(pthread_attr_t *attr,size_t stacksize)
设置栈的大小
设置栈的大小不能小于STACK_MIN
2.2.3栈溢出扩展内存大小
这个属性默认值是由系统决定的,常用值是系统的页大小
当对栈的属性即2.2.2进行了操作的时候,进行了设置,这个属性值就没有用了,因为我们自己进行栈的管理
(1)int pthread_attr_getguardsize(const pthread_attr_t *restrict attr,size_t *restrict guardsize);
对栈的缓冲去进行观察
(2)int pthread_attr_setguardsize(pthread_attr_t *attr,size_t guardsize);
对缓冲区进行设置
3同步属性
定义:同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。 我们平时经常讨论的同步问题多发生在多线程环境中的数据共享问题。即当多个线程需要访问同一个资源时,它们需要以某种顺序来确保该资源在某一特定时刻只能 被一个线程所访问,如果使用异步,程序的运行结果将不可预料。因此,在这种情况下,就必须对数据进行同步,即限制只能有一个进程访问资源,其他线程必须等待。
实现同步的机制主要有临界区、互斥、信号量和事件
4互斥量属性
4.1互斥量属性进行初始化
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
用来对互斥量属性进行初始化,初始化后的属性是默认的
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
用来对互斥量属性进行销毁
列如::::
pthread_mutexattr_t arg;
pthread_mutexattr_init(&arg);
4.2进程共享属性
进程共享属性是可选的,有的系统是不支持的,需要使用sysconf进行判断,_SC_THREAD_PROCESS_SHARED进行判断是否支持共享属性的支持
什么叫做共享属性?注意,当多个线程可以访问同一个同步对象,这里规定的是在进程中实现的,在不同的进程中是不能实现的,要是想要在不同的进程中实现,需要进行设置PTHREAD_PROCESS_SHARED,如果只是在同一个进程中的多个线程中共享的话,只是设置为PTHREAD_PROCESS_PRIVATE;如果设置成共享的话,就可以用来同步进程!!!
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,int pshared);
这个函数是用来设置的共享属性的
attr:代表互斥量属性
pshared:PTHREAD_PROCESS_SHARED(进程共享属性),PTHREAD_PROCESS_PRIVATE(进程私有属性)在这种情况下,pthread线程库提供有效的互斥量实现,这是多线程的默认互斥量进程共享属性
int pthread_mutexattr_getshared(const pthread_mutexattr_t *restrict attr,int *restrict pshared);
用来得到互斥量属性中的进程共享属性设置
4.3互斥量健壮属性
健壮属性的前提条件下,是必须首先设置进程共享属性为PTHREAD_PROCESS_SHARED为什么需要健壮属性,因为互斥量在多个进程中共享,当在一个进程中占有某一个互斥量的时候,这是这个进程死掉了,此时运行B进程,B进程会因为被拖住(这是因为健壮属性被设置为PTHREAD_MUTEX_STALLED,情况相当与死锁),当健壮属性被设置成PTHREAD_MUTEX_ROBUST的时候,此时如果在发生上诉情况,进程B就不会被拖住,应该进行继续运行,但是,pthread_mutex_lock()返回的是EOWNERDEAD,也是进程继续运行
int pthread_mutexattr_setrobust(pthread_mutexattr_t *attr,int robust);
设置健壮属性
attr:互斥量属性对象
robust:PTHREAD_MUTEX_ROBUST(健壮属性),PTHREAD_MUTEX_STALLED(非健壮属性)
int pthread_mutexattr_getrobust(const pthread_mutexattr_t *restrict attr,int *restrict robust)
得到互斥量属性关于健壮性的取值
int pthread_mutex_consistent(pthread_mutex_t *mutex);如果持有 mutex 的进程退出,另外一个线程在 pthread_mutex_lock 的时候会返回 EOWNERDEAD。这时候你需要调用 pthread_mutex_consistent 函数来清除这种状态,否则后果自负。
写成代码就是这样子:
int r = pthread_mutex_lock(lock);
if (r == EOWNERDEAD)
pthread_mutex_consistent(lock);
4.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);
type是上方的几个值
在这里面要特别注意递归变量,在同一个线程中,不要在条件变量的互斥量使用递归!!!比如说如果在使用互斥量(此时递归属性)在使用条件变量的之前(多次进行锁的占有),比如所互斥量已经锁定,此时条件变量不能接受条件的变换,导致死锁!!!!
5读写锁属性
5.1初始化
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
对读写锁属性变量进行初始化
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
销毁读写锁属性变量
5.2进程共享属性
进程贡献属性与互斥量的进程共享属性是一致的
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *restrict attr,int *restrict pshared);
取得读写锁属性的进程共享属性的取值
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr,int pshared);
设置读写锁属性的进程共享属性的取值
6条件变量属性
6.1初始化条件变量属性
int pthread_condattr_init(pthread_condattr_t *attr);
进行条件变量属性的初始化
int pthread_condattr_destroy(pthread_condattr_t *attr);
进行条件变量属性的销毁
6.2进程共享属性
int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr,int *restrict pshared);
和互斥量进程共享属性一致
int pthread_condattr_setpshared(pthread_condattr_t *attr,int pshared);
和互斥量进程共享属性一直
6.3时钟属性
int pthread_condattr_setclock(pthread_condattr_t *attr,clockid_t clock_id);
进行时钟id的选择,这些时钟来源于6-8,这些时钟用于pthread_cond_timewait()等待的时钟
int pthread_condattr_getclock(pthread_condattr_t *attr,clockid_t clock_id);
得到属性的时钟id
7屏障属性
7.1对屏障属性进行初始化和销毁
int pthread_barrierattr_init(pthread_barrierattr_t *attr);
attr:屏障属性对象
int pthread_barrierattr_detroy(pthread_barrierattr_t *attr);
attr:将要销毁的屏障属性对象
7.2屏障进程共享属性
int pthread_barrierattr_getshared(const pthread_barrierattr_t *restrict attr,int *restrict pshared);
attr:屏障属性对象
pshared:看是否支持屏障属性对象属性
int pthread_barrierattr_setshared(pthread_barrierattr_t *attr,int *pshared);
pshared:PTHREAD_PROCESS_SHARED(进程共享属性),PTHREAD_PROCESS_PRIVATE(进程私有属性)在这种情况下,pthread线程库提供有效的互斥量实现,这是多线程的默认互斥量进程共享属性
8重入
8.1什么是线程安全以及哪些情况造成线程不安全
8.1.1线程安全:一个函数被称为线程安全的(thread-safe),当且仅当被多个并发进程反复调用时,它会一直产生正确的结果。如果一个函数不是线程安全的,我们就说它是线程不安全的(thread-unsafe)。我们定义四类(有相交的)线程不安全函数。
8.1.2四类不安全的线程函数
(1)不保护共享变量的函数
将这类线程不安全函数变为线程安全的,相对比较容易:利用像P和V操作这样的同步操作来保护共享变量。这个方法的优点是在调用程序中不需要做任何修改,缺点是同步操作将减慢程序的执行时间。
(2)保持跨越多个调用的状态函数
一个伪随机数生成器是这类不安全函数的简单例子。
unsigned int next = 1; int rand(void) { next = next * 1103515245 + 12345; return (unsigned int) (next / 65536) % 32768; } void srand(unsigned int seed) { next = seed; }
rand函数是线程不安全的,因为当前调用的结果依赖于前次调用的中间结果。当我们调用srand为rand设置了一个种子后,我们反复从一个单线 程中调用rand,我们能够预期一个可重复的随机数字序列。但是,如果有多个线程同时调用rand函数,这样的假设就不成立了。
使得rand函数变为线程安全的唯一方式是重写它,使得它不再使用任何静态数据,取而代之地依靠调用者在参数中传递状态信息。这样的缺点是,程序员现在要被迫改变调用程序的代码。
(3)返回指向静态变量指针的函数
某些函数(如gethostbyname)将计算结果放在静态结构中,并返回一个指向这个结构的指针。如果我们从并发线程中调用这些函数,那么将可能发生灾难,因为正在被一个线程使用的结果会被另一个线程悄悄地覆盖了。
有两种方法来处理这类线程不安全函数。一种是选择重写函数,使得调用者传递存放结果的结构地址。这就消除了所有共享数据,但是它要求程序员还要改写调用者的代码。
如果线程不安全函数是难以修改或不可修改的(例如,它是从一个库中链接过来的),那么另外一种选择就是使用lock-and-copy(加锁-拷 贝)技术。这个概念将线程不安全函数与互斥锁联系起来。在每个调用位置,对互斥锁加锁,调用函数不安全函数,动态地为结果非配存储器,拷贝函数返回的结果 到这个存储器位置,然后对互斥锁解锁。一个吸引人的变化是定义了一个线程安全的封装(wrapper)函数,它执行lock-and-copy,然后调用 这个封转函数来取代所有线程不安全的函数。例如下面的gethostbyname的线程安全函数。
struct hostent* gethostbyname_ts(char* host) { struct hostent* shared, * unsharedp; unsharedp = Malloc(sizeof(struct hostent)); P(&mutex) shared = gethostbyname(hostname); *unsharedp = * shared; V(&mutex); return unsharedp; }
(4)调用线程不安全函数的函数
如果函数f调用线程不安全函数g,那么f就是线程不安全的吗?不一定。如果g是类2类函数,即依赖于跨越多次调用的状态,那么f也是不安全的,而且 除了重写g以外,没有什么办法。然而如果g是第1类或者第3类函数,那么只要用互斥锁保护调用位置和任何得到的共享数据,f可能仍然是线程安全的。比如上 面的gethostbyname_ts。
8.2POSIX中不能保证线程安全的函数
上图都是不能保证重入成功的函数针对次,POSIX有了替代的线程安全函数
图12-10是代替的线程可重入函数
POSIX还提供了一线程安全的方式管理FILE对象的方法
#include <stdio.h>
void flockfile(FILE *fp);
对于FILE对象获取关联的锁,就是占用锁,这个锁是递归的,此线程在占用了这把锁的时候可以继续占用此把锁,不会发生阻塞导致死锁。注意这种锁的实现并没有要求,但是我们平时使用的操作FILE对象的函数都使用了次类型锁,但是没有强求必须使用flockfile,但是作用是一样的(这个函数基本在应用中不适用)
int ftrylockfile(FILE *fp);
尝试获得锁,如果失败,则返回非0值,若成功,则返回0
void funlockfile(FILE *fp);
对锁进行解锁
注意!!这样在每一个操作
FILE对象的函数调用(库提供)这样使用锁会使效率降低,因此我们把锁的控制权放给用户会提高效率
,但是要注意使用锁的范围
#include <stdio.h>
int getchar_unlocked(void);
不加锁版本,若成功返回下一个字符,如果文件尾或者出错,返回EOF
int getc_unlocked(FILE *fp);
不加锁版本,若成功返回下一个字符,如果文件尾或者出错,返回EOF
int putchar_unlocked(int c);
不加锁版本,输出一个字符,向标准流中,若成功,返回c,失败返回EOF
int putc_unlocked(int c,FILE *fp);
不加锁版本,输出一个字符,向标准流中,若成功,返回c,失败返回EOF
这几个函数,必须在flockfile和funlockfile中的调用中包围,否则尽量不要使用这几个函数
9线程私有数据
9.1 概念及作用
在单线程程序中,我们经常要用到"全局变量"以实现多个函数间共享数据。在多线程环境下,由于数据空间是共享的,因此全局变量也为所有线程所共有。现在有一全局变量,所有线程都可以使用它,改变它的值。而如果每个线程希望能单独拥有它,那么就需要使用线程存储了。表面上看起来这是一个全局变量,所有线程都
可以使用它,而它的值在每一个线程中又是单独存储的。这就是线程存储的意义。这样的数据结构可以由Posix线程库维护,称为线程私有数据
(Thread-specific
Data,或TSD)。
具体用法如下:
(1)创建一个类型为pthread_key_t类型的变量。
(2)调用pthread_key_create()来创建该变量。该函数有两个参数,第一个参数就是上面声明的pthread_key_t变量,第二个参数是一个清理函数,用来在线程释放该线程存储的时候被调用。该函数指针可以设成NULL,这样系统将调用默认的清理函数。
(3)当线程中需要存储特殊值的时候,可以调用pthread_setspcific()。该函数有两个参数,第一个为前面声明的pthread_key_t变量,第二个为void*变量,这样你可以存储任何类型的值。
(4)如果需要取出所存储的值,调用pthread_getspecific()。该函数的参数为前面提到的pthread_key_t变量,该函数返回void *类型的值。
9.2 创建和注销
int pthread_key_create(pthread_key_t *key, void (*destr_function)
(void *));
该函数从TSD池(里面存储了很多key的值)中分配一项,将其值赋给key供以后访问使用。如果destr_function不为空,在线程退出(pthread_exit())时将以key所关联的数据为参数调用destr_function(),以释放分配的缓冲区。
不论哪个线程调用pthread_key_create(),所创建的key都是所有线程可访问的但各个线程可根据自己的需要往key中填入不同的值,这就相当于提供了一个同名而不同值的全局变量。在LinuxThreads的实现中,TSD池用一个结构数组表示:
static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] = {
{ 0, NULL } };
创建一个TSD就相当于将结构数组中的某一项设置为"in_use",并将其索引返回给*key,然后设置destructor函数为destr_function。
注销一个TSD采用如下API:
int pthread_key_delete(pthread_key_t key);
这个函数并不检查当前是否有线程正使用该TSD,也不会调用清理函数(destr_function),而只是将TSD释放以供下一次调用
pthread_key_create()使用。在LinuxThreads中,它还会将与之相关的线程数据项设为NULL(见"访问")。
9.3访问
int pthread_setspecific(pthread_key_t key, const void
*pointer);
写入(pthread_setspecific())时,将pointer的值(不是所指的内容)与key相关联,而相应的读出函数则将与key相关联的数据读出来。数据类型都设为void *,因此可以指向任何类型的数据。
void * pthread_getspecific(pthread_key_t key) ;
得到访问线程的key(指定的)中数据
10取消选项
10.1取消
其实取消选项是线程的两个属性,但是并没有包含在pthread_attr_t结构中,这两个属性是可取消状态和可取消类型,这两个属性注意影响的是调用pthread_cancel 的行为
10.2可取消状态
int pthread_setcancelstate(int state,int *oldstate);
state:可取消状态,第一种是PTHREAD_CANCEL_DISABLE禁止可取消状态,当选用次状态的时候,在另一个线程的取消检测点,检测到取消,但是并不取消线程,取消请求处于挂起状态;第二种是PTHREAD_CANCEL_ENABLE这个时候调用pthread_cancel的时候在检测点杀死线程
oldstate:保存的是上一次取消状态属性
注意,当一个进程被设置为PTHREAD_CANCEL_DISABLE时候,这个时候在把他设置成PTHREAD_CANCEL_ENABLE,线程这个时候将在下一个检测点的时候将线程进行取消
void pthread_testcancel(void);
设置一个检测点,如果在一个具体的函数调用中长时间运行,则需要设置这个检测点
检测点的起效!!某个取消请求正处于挂起状态,并且取消并没有设为无效,那么就会线程被取消
如果取消被设置为无效,则没有用这个检测点
10.3设置取消点检测的函数
10.4可取消类型
int pthread_setcanceltype(int type, int *oldtype)
注意:我们默认的可取消类型是推迟取消,调用pthread_cancel()以后,并不会出现真正的取消,相反要等到一个检测点的时候,
type:PTHREADCANCEL_DEFERRED延迟取消,PTHREAD_CANCEL_ASYNCHRONOUS立即取消
oldtype:这个是将原来的取消方式存入oldtype中
11线程和信号
每个线程都有自己的信号屏蔽字,但是每个线程如果修改线程屏蔽字,则所有的线程共享此线程屏蔽字,进程中的信号是递送到单个线程中的,如果一个信号是与硬件故障有关,则这个信号一般被送到引起这个故障的线程中
11.1线程信号屏蔽字
#include <signal.h>
int pthread_sigmask(int how,const sigset_t *restrict set,sigset_t *restrict oset);
how参数取值:
SIG_BLOCK:把信号添加到线程信号屏蔽字中
SIG_SETMASK:用信号集替换信线程的信号屏蔽字
SIG_UNBLOCK:从信号屏蔽字移除信号集中
set:信号集
oset:原来的信号集
int sigwait(const sigset_t *restrict set,int *restrict signop);
功能:如果某个信号在调用sigwait的时候是挂起状态,则sigwait将无阻塞返回,在返回之前,sigwait将从进程中删除那些处于挂起等待状态的信号,如果支持排队,并且多个信号实例被挂起,sigwait将会移除该信号的一个实例,其他的实例还要继续排队
为了避免错误的发生,在sigwait之前,每一个线程都应该阻塞那些他正在等待的信号(实则是将这些阻塞的信号送往一个叫做信号处理的线程),sigwait函数被调用之后马上原子性的取消阻塞状态,直到有一个新的信号被递送之后,恢复阻塞状态,并且sigwait在返回之前,恢复信号屏蔽字
通过以上方式,sigwait其实做的就是把异步信号处理方式,改变成了同步信号处理方式
set:线程等待的信号集
signorp:当调用一次sigwait的时候,并且有set指定的信号阻塞(两个以上)signorp指的是最近被递送的那一个信号
int pthread_kill(pthread_t thread,int signo);
thread:线程的ID
signo:信号的名字
12线程与fork
12.1相关概念关系以及存在问题
fork函数调用会创建子进程,子进程的地址空间是在调用fork时父进程地址空间的拷贝。因为子进程地址空间跟父进程一样,所以调用fork时,子进程继承了父进程中的所有互斥锁、读写锁和条件变量(包括它们的状态)。
但在多线程环境中,调用fork时,子进程中只有一个线程存在,这个线程是调用fork函数的那个线程,其他线程都没有被拷贝。
根据上述两点,子进程中的锁可能被不存在的线程所拥有,这样子进程将没法获取或释放这些锁。针对这个问题有一个解决办法,即在调用fork之前,线程先获 取进程中所有锁,在调用fork后分别在父子进程中释放这些锁,从而可以重新利用这些资源。因为fork之前,当前线程拥有所有的锁,所以fork之后, 当前线程继续存在,子进程可以安全的释放这些锁。
当然,在调用fork后,子进程马上调用exec(在exec和fork之后的只能调用异步信号安全函数),就无需考虑这些问题了,因为子进程地址空间被完全更换了。
12.2解决上诉问题函数
函数pthread_atfork专门用来解决这种问题:
int pthread_atfork ( void (*prepare)(void), void (*parent)(void), void (*child)(void) );
pthread_atfork安装一些在fork调用时的回调函数。
prepare函数将在fork创建子进程之前被调用,通常可以用来获取进程中的所有 锁;
parent在fork创建子进程后返回前在父进程中被调用,可以用来释放父进程中的锁;
child在fork创建子进程后fork返回前在子进程中 被调用,可以用来释放子进程中的锁。
给这三个参数传递NULL,表示不调用该函数。
可以调用pthread_atfork多次注册多组回调函数,这时,回调函数调用的顺序规定如下:
①prepare函数调用顺序与它们的注册顺序相反;
②parent和child函数的调用顺序与注册顺序相同。