多线程程序里不准使用fork

2016/05/09 21:13
阅读数 1K

多线程程序里不准使用fork

一般的,fork做如下事情

  1. 父进程的内存数据会原封不动的拷贝到子进程中
  2. 子进程在单线程状态下被生成

在内存区域里,静态变量 mutex 的内存会被拷贝到子进程里。而且,父进程里即使存在多个线程,但它们也不会被继承到子进程里。fork 的这两个特征就是造成死锁的原因。

#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <pthread.h>

void *doit(void *arg)
{
    static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

    pthread_mutex_lock(&mutex);
    fprintf(stderr, "lock... pid = %d\n", getpid());

    /* 线程持有锁,进入睡眠 */
    struct timespec tc = {10, 0};
    nanosleep(&tc, 0);

    fprintf(stderr, "unlock... pid = %d\n", getpid());
    pthread_mutex_unlock(&mutex);
    
    return NULL;
}

int main(int argc, char **argv)
{
    pid_t pid;
    pthread_t   t;
    struct timespec tc = {2, 0};
    
    pthread_create(&t, 0, doit, 0);

    // sleep,让线程执行 doit() 并占有锁
    nanosleep(&tc, 0);

    if ((pid = fork()) == 0) {
        /* 在线程持有锁期间进行fork,在子进程中,线程将会占有锁并死去 */
        fprintf(stderr, "[child] fork...\n");
        /* 再次试图加锁,此时进入死锁 */
        doit(NULL);
        return 0;
    }

    fprintf(stderr, "[parent] child pid = %d\n", pid);

    pthread_join(t, 0);

    return 0;
}

死锁原因的详细解释 ---

  1. 线程里的 doit() 先执行
  2. doit() 执行的时候会给互斥体变量 mutex 加锁
  3. mutex 变量的内容会原样拷贝到 fork 出来的子进程中(在此之前,mutex 变量的内容已经被线程改写成锁定状态)
  4. 子进程再次调用 doit() 的时候,在锁定互斥体 mutex 的时候会发现它已经被加锁,所以就一直等待,直到拥有该互斥体的进程释放它(实际上没有人拥有这个 mutex 锁)
  5. 线程的 doit() 执行完成之前会把自己的 mutex 释放,但这是的 mutex 和子进程里的 mutex 已经是两份内存。所以即使释放了 mutex 锁也不会对子进程里的 mutex 造成什么影响

像这里的 doit() 函数那样的,在多线程里因为 fork 而引起问题的函数,我们把它叫做“fork-unsafe函数”。反之,不能引起问题的函数叫做“fork-safe函数”。

随便说一下,malloc 函数就是一个维持自身固有 mutex 的典型例子,通常情况下它是 fork-unsafe 的。依赖于 malloc 函数的函数有很多,例如 printf 函数等,也是变成 fork-unsafe 的。

直到目前为止,已经写上了thread+fork是危险的,但是有一个特例需要告诉大家“fork 后马上调用 exec 的场合,是作为一个特列不会产生问题的”。exec 函数一被调用,进程的“内存数据”就被临时重置成非常漂亮的状态。因此,即使在多线程状态的进程里,fork 后不马上调用一切危险的函数,只是调用 exec 的话,子进程将不会产生任何的误动作。

如何规避灾难呢?

规避方法1:做fork的时候,在它之前让其他的线程完全终止

规避方法2:fork后在子进程中马上调用exec函数

规避方法3:“其他线程”中,不做fork-unsafe的处理

规避方法4: 使用pthread_atfork函数,在即将fork之前调用事先准备的回调函数

规避方法5:在多线程程序里,不使用 fork

参考

http://www.cppblog.com/lymons/archive/2008/06/01/51836.html

展开阅读全文
打赏
2
1 收藏
分享
加载中
更多评论
打赏
0 评论
1 收藏
2
分享
返回顶部
顶部