多线程的父进程调用 fork 函数创建子进程时,子进程继承了整个地址空间的副本。子进程里面只有一个线程,它是父进程中调用 fork 函数的线程的副本。在子进程中的线程继承了在父进程中相同的状态,即有相同的互斥量、读写锁和条件变量。如果父进程中的线程占用锁,则子进程也同样占有这些锁,只是子进程不包含占有锁的线程的副本,所以并不知道具体占有哪些锁并且需要释放哪些锁。
如果子进程从 fork 返回之后没有立即调用 exec 函数,则需要调用 fork 处理程序清理锁状态。可以调用 pthread_atfork 函数实现清理锁状态:
/* 线程和 fork */ /* * 函数功能:清理锁状态; * 返回值:若成功则返回0,否则返回错误编码; * 函数原型: */ #include <pthread.h> int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void)); /* * 说明: * 该函数最多可以安装三个帮助清理锁的函数; * prepare fork处理程序由父进程在fork创建子进程前调用,这个fork处理程序的任务是获取父进程定义的所有锁; * * parent fork处理程序是在fork创建子进程以后,但在fork返回之前在父进程环境中调用的,这个fork处理程序的任务是对prepare fork处理程序 * 获取的所有锁进行解锁; * * child fork处理程序在fork返回之前在子进程环境中调用,与parent fork处理程序一样,child fork处理程序必须释放prepare fork处理程序获得的所有锁; */
例如,模块A调用模块B中的函数,而且每个模块有自己的一套锁。如果所的层次是A在B之间,模块B必须在模块A之前设置fork处理程序,当父进程调用fork时,就会执行以下步骤,假设子进程在父进程之前运行。
1.调用模块A的 prepare 处理程序获取模块A的所有锁。
2.调用模块B的 prepare 处理程序获取模块B的所有锁。
3.创建子进程。
4.调用模块B中的 child 处理程序释放子进程中模块B的所有锁。
5.调用模块A中的 child 处理程序释放子进程中模块A的所有锁。
6.fork 函数返回到子进程。
7.调用模块B中的 parent 处理程序释放子进程中模块B的所有锁。
8.调用模块A中的 parent 处理程序释放子进程中模块A的所有锁。
9.fork 函数返回到父进程。
测试程序:
#include "apue.h" #include <pthread.h> #include <signal.h> pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER; void prepare(void) { printf("preparing locks...\n"); pthread_mutex_lock(&lock1); pthread_mutex_lock(&lock2); } void parent(void) { printf("parent unlocking locks...\n"); pthread_mutex_unlock(&lock1); pthread_mutex_unlock(&lock2); } void child(void) { printf("child unlocking locks...\n"); pthread_mutex_unlock(&lock1); pthread_mutex_unlock(&lock2); } void* thread_func(void *arg) { printf("thread started...\n"); pause(); return 0; } int main(void) { pid_t pid; pthread_t tid; int err; err = pthread_atfork(prepare,parent,child); if(err != 0) err_exit(err, "can't install fork handlers"); err = pthread_create(&tid,NULL,thread_func,NULL); if(err != 0) err_exit(err, "can't create thread"); sleep(2); printf("parent about to fork.\n"); pid = fork(); if(pid == -1) err_quit("fork failed: %s\n", strerror(err)); if(pid == 0) printf("child returned from fork.\n"); else printf("parent returned form fork.\n"); exit(0); }
thread started... parent about to fork. preparing locks... parent unlocking locks... parent returned form fork. child unlocking locks... child returned from fork.
参考资料:
《UNIX高级环境编程》