函数的原型是这样的:
include <pthread.h>
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));
说明:该函数用于注册三个回调函数,后面用户调用fork函数时,才真正执行。调用fork时,内部创建子进程前在父进程中会调用prepare,内部创建子进程(fork)成功后,父进程会调用parent ,子进程会调用child。
下面有一段代码:
#include <stdio.h>
#include <time.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* doit(void* arg)
{
printf("pid = %d begin doit ...\n",static_cast<int>(getpid()));
pthread_mutex_lock(&mutex); // 3.加锁
struct timespec ts = {2, 0};
nanosleep(&ts, NULL);
pthread_mutex_unlock(&mutex);
printf("pid = %d end doit ...\n",static_cast<int>(getpid()));
return NULL;
}
void prepare(void)
{
pthread_mutex_unlock(&mutex); // 5. 注释该行会死锁
}
void parent(void)
{
pthread_mutex_lock(&mutex);
}
int main(void)
{
pthread_atfork(prepare, parent, NULL); // 1.注册函数,注释后会死锁
printf("pid = %d Entering main ...\n", static_cast<int>(getpid()));
pthread_t tid;
pthread_create(&tid, NULL, doit, NULL); // 2.创建线程
struct timespec ts = {1, 0};
nanosleep(&ts, NULL);
if (fork() == 0) // 4. 创建进程,当为子进程时
{
doit(NULL); // 6. 在子进程中执行
}
pthread_join(tid, NULL);
printf("pid = %d Exiting main ...\n",static_cast<int>(getpid()));
return 0;
}
准确来说,这段代码是为了演示程序中线程和进程混用容易导致可能的死锁问题。代码执行流程如下:
首先,在1处注册函数;然后,2处创建了一个线程执行doit函数,doit里3处对mutex进行加锁。为了显示死锁问题,到这里故意睡眠一段时间。然后主线程执行到4,开始fork子进程,这时,在创建子进程前先执行prepare函数,这里进行解锁,之后执行parent函数加锁。后面2处的线程继续执行解锁,6处子进程执行doit,都是OK的。
这里,如果将1处注释,程序仍然按上面流程执行,线程2->3,加锁;子进程进行到4->6->3时,一直等待mutex解锁,造成死锁。
根本原因在于:父进程创建一个子进程时,子进程会复制父进程的内存,包括mutex锁的状态。当mutex处于加锁状态时,子进程死锁等待。
下面是可能的死锁执行结果:
pid = 7714 Entering main ...
pid = 7714 begin doit ...
pid = 7719 begin doit ...
pid = 7714 end doit ...
pid = 7714 Exiting main ...