我们先来看一个程序:
#include <pthread.h> #include <stdio.h> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; void* th_fn(void* arg) { printf("start th_fn.\n"); pthread_mutex_lock(&mutex); printf("in th_fn.\n"); sleep(5); pthread_mutex_unlock(&mutex); printf("end th_fn.\n"); return 0; } void ps_fn(){ printf("start ps_fn.\n"); pthread_mutex_lock(&mutex); printf("in ps_fn.\n"); sleep(5); pthread_mutex_unlock(&mutex); printf("end ps_fn.\n"); } int main(void) { pthread_t t; pthread_create(&t, 0, th_fn, 0); sleep(2); if (fork() == 0) { ps_fn(); return 0; } pthread_join(t, 0); while(1){ sleep(1); } }
执行结果:
# ./a.out
start th_fn.
in th_fn.
start ps_fn.
end th_fn.
ps_fn函数一直都没有获取互斥锁,所以该函数一直都没有结束。
分析下原因:
1.线程里的th_fn先执行,给互斥变量mutex加锁。
2.mutex变量的内容会原样拷贝到fork出来的子进程中,在子进程中mutex的变量的已经被线程改写成锁定状态。
3.子进程调用ps_fn,在锁定互斥体mutex的时候会发现它已经被加锁,所以就一直等待,直到拥有该互斥体的进程释放它。
但是实际上没有人拥有这个mutex锁。
4.线程th_fn执行完之前会把自己的mutex释放,但是这里的mutex和子进程里的mutex已经是两份内存,所以即使释放了
mutex锁也不会对子进程里的mutex造成什么影响。
要规避这个问题的一种方法是子进程从fork返回后马上调用某个exec函数,就可以避免这样的问题,这种情况下,老的地址
空间被丢弃,所以锁的状态无关紧要,但是如果进程需要继续做处理工作的话,这种方式是行不通的。另一种策略是使用
pthread_atfork函数。
#include<pthread.h> int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void)); //成功则返回0,否则返回错误编号该函数最多可以安装三个帮助清理的函数。
prepare处理程序由父进程在fork创建子进程前调用。
parent处理程序在fork创建子进程以后,但在fork返回之前在父进程环境中调用。
child处理程序在fork返回之前在子进程环境中调用。
可以多次调用pthread_atfork函数从而设置多套fork处理程序。如果不需要使用其中某个处理程序,可以给特定的处理程序
参数传入空指针,这样就不会起任何作用作用。使用多个fork处理程序时,处理程序的调用顺序并不相同。parent和child
处理程序时以它们注册时的顺序进行调用的。而prepare处理程序的调用顺序与它们注册的顺序相反,这样可以允许多个
模块注册它们自己的fork处理函数,并且保持锁的层次。
例如,模块A调用模块B中的函数,而且每个模块有自己的一套锁。如果所的层次是A在B之间,模块B必须在模块A之前设置
fork处理程序,当父进程调用fork时,就会执行以下步骤,假设子进程在父进程之前运行。
1.调用模块A的prepare处理程序获取模块A的所有锁。
2.调用模块B的prepare处理程序获取模块B的所有锁。
3.创建子进程。
4.调用模块B中的child处理程序释放子进程中模块B的所有锁。
5.调用模块A中的child处理程序释放子进程中模块A的所有锁。
6.fork函数返回到子进程。
7.调用模块B中的parent处理程序释放子进程中模块B的所有锁。
8.调用模块A中的parent处理程序释放子进程中模块A的所有锁。
9.fork函数返回到父进程。
下面我们修改下程序:
#include <pthread.h> #include <stdio.h> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; void child(void){ pthread_mutex_unlock(&mutex); } void* th_fn(void* arg) { printf("start th_fn.\n"); pthread_mutex_lock(&mutex); printf("in th_fn.\n"); sleep(5); pthread_mutex_unlock(&mutex); printf("end th_fn.\n"); return 0; } void ps_fn(){ printf("start ps_fn.\n"); pthread_mutex_lock(&mutex); printf("in ps_fn.\n"); sleep(5); pthread_mutex_unlock(&mutex); printf("end ps_fn.\n"); } int main(void) { pthread_t t; pthread_atfork(NULL,NULL,child); pthread_create(&t, 0, th_fn, 0); sleep(2); if (fork() == 0) { ps_fn(); return 0; } pthread_join(t, 0); while(1){ sleep(1); } }运行结果:
yan@yan-vm:~/apue$ ./a.out
start th_fn.
in th_fn.
start ps_fn.
in ps_fn.
end th_fn.
end ps_fn.