多线程程序里尽量不使用fork
在多线程程序里,在”自身以外的线程存在的状态”下一使用fork的话,就可能引起各种各样的问题.比较典型的例子就是,fork出来的子进程可能会死锁.请不要,在不能把握问题的原委的情况下就在多线程程序里fork子进程,能引起什么问题呢?
那看看实例吧.一执行下面的代码,在子进程的执行开始处调用doit()时,发生死锁的机率会很高.
void* doit(void*) {
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex);
struct timespec ts = {10, 0}; nanosleep(&ts, 0); // 10秒
// 睡10秒
pthread_mutex_unlock(&mutex);
return 0;
}
int main(void) {
pthread_t t;
pthread_create(&t, 0, doit, 0); // 做成并启动子线程
if (fork() == 0) {
//子进程
//在子进程被创建的瞬间,父的子进程在执行nanosleep的场合比较多
doit(0);
return 0;
}
pthread_join(t, 0);
// 等待子线程结束
}
以下是说明死锁的理由:
一般的,fork做如下事情
1. 父进程的内存数据会原封不动的拷贝到子进程中
2. 子进程在单线程状态下被生成
在内存区域里,静态变量mutex的内存会被拷贝到子进程里.而且,父进程里即使存在多个线程,但它们也不会被继承到子进程里. fork的这两个特征就是造成死锁的原因.
译者注: 死锁原因的详细解释 ---
1. 线程里的doit()先执行.
2. doit执行的时候会给互斥体变量mutex加锁.
3. mutex变量的内容会原样拷贝到fork出来的子进程中(在此之前,mutex变量的内容已经被线程改写成锁定状态).
4.子进程再次调用doit的时候,在锁定互斥体mutex的时候会发现它已经被加锁,所以就一直等待,直到拥有该互斥体的进程释放它(实际上没有人拥有这个mutex锁).
5.线程的doit执行完成之前会把自己的mutex释放,但这是的mutex和子进程里的mutex已经是两份内存.所以即使释放了mutex锁也不会对子进程里的mutex造成什么影响.
例如,请试着考虑下面那样的执行流程,就明白为什么在上面多线程程序里不经意地使用fork就造成死锁了*3.
1. 在fork前的父进程中,启动了线程1和2
2. 线程1调用doit函数
3. doit函数锁定自己的mutex
4. 线程1执行nanosleep函数睡10秒
5. 在这儿程序处理切换到线程2
6. 线程2调用fork函数
7. 生成子进程
8. 这时,子进程的doit函数用的mutex处于”锁定状态”,而且,解除锁定的线程在子进程里不存在
9. 子进程的处理开始
10. 子进程调用doit函数
11. 子进程再次锁定已经是被锁定状态的mutex,然后就造成死锁