多线程程序中的fork调用

        多线程程序执行fork调用是一个比较复杂的问题。首先fork调用的行为大概是复制一个和父进程一样的子进程,然后两个进程以不同的值返回。理论上,父子进程应该是非常相似的。fork调用在单线程上是非常容易理解的,在多线程上就不太好理解了。在多线程程序里存在着多个执行流,换句话说,就是fork()调用点只是一个程序的一个并行执行分支而已。说了这么多,其实真相非常简单。下面是《POSIX多线程程序设计》中的原话:“当多线程进程调用fork创造子进程时,Pthreads指定只用那个调用fork的线程在子进程中存在。”换句话说,就是fork得到的子进程只有一个执行流。
        好吧!感觉问题简单多了。但是好像有点不太对劲,一个进程的全部信息不仅仅包括执行流(对应堆栈),还有堆空间数据、bss段和data段等等。这些数据是多线程程序中各个进程共有的,比如一些全局变量。换句话说,就是fork会把这些数据也复制到子进程中。那么问题就来了,比如说一个多线程程序中有一个全局互斥锁。fork会把互斥锁一起复制到子进程中,当然互斥锁的状态也是和复制时父进程中一样的。如果复制时的互斥锁是锁住的,那么程序的逻辑很可能被破坏。因为子进程中只有一个执行流,互斥锁如果不是执行fork的线程锁住的,那结果将是永远锁住,锁住互斥锁的线程在新进程中根本就不存在了。
        所以POSIX对这个问题提供了一个解决方案,这就是pthread_atfork。具体的东西一看代码和注释便知。
atfork注释
/*
 * atfork.c
 *
 * Demonstrate the use of "fork handlers" to protect data
 * invariants across a fork.
 */
#include <sys/types.h>
#include <pthread.h>
#include <sys/wait.h>
#include "errors.h"

pid_t self_pid;                         /* pid of current process */
//主进程的id
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

/*
 * This routine will be called prior to executing the fork,
 * within the parent process.
 */
//在fork()调用之间,父进程会调用这个fork_prepare()函数
//锁住互斥量
void fork_prepare (void)
{
    int status;

    /*
     * Lock the mutex in the parent before creating the child,
     * to ensure that no other thread can lock it (or change any
     * associated shared state) until after the fork completes.
     */
    status = pthread_mutex_lock (&mutex);
    if (status != 0)
        err_abort (status, "Lock in prepare handler");
}

/*
 * This routine will be called after executing the fork, within
 * the parent process
 */

/*
在fork之后,父进程会调用这个fork_parent()函数,这里是简单的解锁的互斥量
 */
void fork_parent (void)
{
    int status;

    /*
     * Unlock the mutex in the parent after the child has been created.
     */
    status = pthread_mutex_unlock (&mutex);
    if (status != 0)
        err_abort (status, "Unlock in parent handler");
}

/*
在fork之后,子进程会调用这个fork_child()函数,这里是简单的解锁的互斥量
并把self_pid(子进程的)的值更改为子进程的pid
 */
void fork_child (void)
{
    int status;

    /*
     * Update the file scope "self_pid" within the child process, and unlock
     * the mutex.
     */
    self_pid = getpid ();
    status = pthread_mutex_unlock (&mutex);
    if (status != 0)
        err_abort (status, "Unlock in child handler");
}

/*
 * 这是主进程的一个线程的执行函数,在这个线程里执行了fork()调用
 */
void *thread_routine (void *arg)
{
    pid_t child_pid;
    int status;

    child_pid = fork ();
    if (child_pid == (pid_t)-1)
        errno_abort ("Fork");

    /*
     * Lock the mutex -- without the atfork handlers, the mutex will remain
     * locked in the child process and this lock attempt will hang (or fail
     * with EDEADLK) in the child.
     */
    //下面这段代码,直到if语句,父进程的子线程和子进程都会执行
    //区别在于child_pid和self_pid都不一样
    //child_pid在fork调用时修改,子进程的self_pid在fork_child()中被修改
    status = pthread_mutex_lock (&mutex);
    if (status != 0)
        err_abort (status, "Lock in child");
    status = pthread_mutex_unlock (&mutex);
    if (status != 0)
        err_abort (status, "Unlock in child");
    printf ("After fork: %d (%d)\n", child_pid, self_pid);
    if (child_pid != 0) {
        if ((pid_t)-1 == waitpid (child_pid, (int*)0, 0))
            errno_abort ("Wait for child");
    }
    return NULL;
}

int main (int argc, char *argv[])
{
    pthread_t fork_thread;
    int atfork_flag = 1;
    int status;

    if (argc > 1)
        atfork_flag = atoi (argv[1]);
    if (atfork_flag) {
        status = pthread_atfork (fork_prepare, fork_parent, fork_child);
        if (status != 0)
            err_abort (status, "Register fork handlers");
    }
    self_pid = getpid ();
    status = pthread_mutex_lock (&mutex);
    if (status != 0)
        err_abort (status, "Lock mutex");
    /*
     * Create a thread while the mutex is locked. It will fork a process,
     * which (without atfork handlers) will run with the mutex locked.
     */
    status = pthread_create (&fork_thread, NULL, thread_routine, NULL);
    if (status != 0)
        err_abort (status, "Create thread");
    sleep (5);
    status = pthread_mutex_unlock (&mutex);
    if (status != 0)
        err_abort (status, "Unlock mutex");
    status = pthread_join (fork_thread, NULL);
    if (status != 0)
        err_abort (status, "Join thread");
    return 0;
}
相关文章
相关标签/搜索