多线程的父进程调用 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处理程序获得的所有锁;
- */
可以多次调用 pthread_atfork 函数从而设置多套 fork 处理程序。如果不需要使用其中某个处理程序,可以给特定的处理程序参数传入空指针,这样就不会起任何作用作用。使用多个 fork 处理程序时,处理程序的调用顺序并不相同。 parent 和 child fork 处理程序时与它们注册时的顺序进行调用的。而 prepare fork 处理程序的调用顺序与它们注册的顺序相反,这样可以允许多个模块注册它们自己的 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.