pthread_atfork函数 -- 多线程程序fork时互斥锁的处理

如果一个多线程程序的某个线程调用了fork函数,那么新创建的子进程是否将自动创建和父进程相同数量的线程呢?
答案是不会,子进程只拥有一个执行线程,它是调用fork的那个线程的完整复制。
并且子进程将自动继承父进程中互斥锁的状态。
也就是说,父进程中已经加锁的互斥锁在子进程中也是被锁住的。
这引来的问题:子进程可能不清楚从父进程中继承而来的互斥锁的具体状态(加锁或者解锁状态未知)。
这个互斥锁可能被加锁了,但并不是由调用fork函数的那个线程锁住的,而是由其他线程锁住的。若是此种情况,子进程若再次对该互斥锁执行加锁操作就会导致死锁。

多线程程序中调用fork函数可能导致死锁程序示例:

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <wait.h>

pthread_mutex_t mutex;

void* another(void*arg)
{
    printf("子线程中,互斥锁上锁\n");
    pthread_mutex_lock(&mutex);
    sleep(5);
    pthread_mutex_unlock(&mutex);
}

int main()
{
    pthread_mutex_init(&mutex, NULL);
    pthread_t id;
    pthread_create(&id, NULL, another, NULL);

    /*确保子线程已经开始运行并获得互斥变量mutex,主线程sleep(1)*/
    sleep(1);

    int pid = fork();
    if(pid < 0)
    {
        pthread_join(id, NULL);
        pthread_mutex_destroy(&mutex);
        return 1;
    }
    else if(pid == 0)//子进程
    {
        printf("子进程, 获取锁\n");
        /* 子进程从父进程继承了互斥锁mutex的状态, 该互斥锁处于锁住状态, 这是由父进程中的子线程执行pthread_mutex_lock引起 因此,下面这句加锁会一直阻塞 */
        pthread_mutex_lock(&mutex);
        printf("阻塞了。。。。\n");
        pthread_mutex_unlock(&mutex);
        exit(0);
    }
    else
    {
        wait(NULL);
    }
    pthread_join(id, NULL);
    pthread_mutex_destroy(&mutex);

    return 0;
}

pthread_atfork函数

pthread提供了一个专门的函数pthread_atfork(),以确保fork调用后父进程和紫禁城都拥有一个清楚地锁状态。该函数的定义如下:

#include <pthread.h>
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));

该函数将建立3个fork句柄来帮助我们清理互斥锁状态。
prepare句柄将在fork调用创建出子进程之前被执行,它可以用来锁住所有父进程中的互斥锁。
parent句柄则是fork调用创建出子进程之后,而fork返回之前,在父进程中被执行。它的作用是释放所有在prepare句柄中被锁住的互斥锁。
child句柄是fork返回之前,在子进程中被执行。和parent句柄一样,child句柄也是用于释放所有在prepare句柄中被锁住的互斥锁。

函数成功返回0, 错误返回错误码。

在上述程序中使用pthread_atfork函数

在fork调用前加入下列代码:

void prepare()
{
    pthread_mutex_lock(&mutex);
}

void infork()
{
    pthread_mutex_unlock(&unlock);
}

pthread_atfork(prepare, infork, infork);

《Linux高性能服务器编程》学习笔记

你可能感兴趣的:(pthread_atfork函数 -- 多线程程序fork时互斥锁的处理)