《unix高级环境编程》线程控制——线程和 fork

        多线程的父进程调用 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 处理程序时,处理程序的调用顺序并不相同。 parentchild 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.

参考资料:

《UNIX高级环境编程》

你可能感兴趣的:(线程和fork)