《UNIX环境高级编程》笔记--线程和fork

我们先来看一个程序:

#include <pthread.h>
#include <stdio.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* th_fn(void* arg) {
        printf("start th_fn.\n");
        pthread_mutex_lock(&mutex);
        printf("in th_fn.\n");
        sleep(5);
        pthread_mutex_unlock(&mutex);
        printf("end th_fn.\n");
        return 0;
}

void ps_fn(){
        printf("start ps_fn.\n");
        pthread_mutex_lock(&mutex);
        printf("in ps_fn.\n");
        sleep(5);
        pthread_mutex_unlock(&mutex);
        printf("end ps_fn.\n");
}

int main(void) {
        pthread_t t;
        pthread_create(&t, 0, th_fn, 0);
        sleep(2);
        if (fork() == 0) {
              ps_fn();
              return 0;
        }
        pthread_join(t, 0);
        while(1){
        sleep(1);
        }
}

执行结果:

# ./a.out
start th_fn.
in th_fn.
start ps_fn.
end th_fn.

ps_fn函数一直都没有获取互斥锁,所以该函数一直都没有结束。

分析下原因:

1.线程里的th_fn先执行,给互斥变量mutex加锁。

2.mutex变量的内容会原样拷贝到fork出来的子进程中,在子进程中mutex的变量的已经被线程改写成锁定状态。

3.子进程调用ps_fn,在锁定互斥体mutex的时候会发现它已经被加锁,所以就一直等待,直到拥有该互斥体的进程释放它。

但是实际上没有人拥有这个mutex锁。

4.线程th_fn执行完之前会把自己的mutex释放,但是这里的mutex和子进程里的mutex已经是两份内存,所以即使释放了

mutex锁也不会对子进程里的mutex造成什么影响。


要规避这个问题的一种方法是子进程从fork返回后马上调用某个exec函数,就可以避免这样的问题,这种情况下,老的地址

空间被丢弃,所以锁的状态无关紧要,但是如果进程需要继续做处理工作的话,这种方式是行不通的。另一种策略是使用

pthread_atfork函数。

#include<pthread.h>
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));
//成功则返回0,否则返回错误编号
该函数最多可以安装三个帮助清理的函数。

prepare处理程序由父进程在fork创建子进程前调用。

parent处理程序在fork创建子进程以后,但在fork返回之前在父进程环境中调用。

child处理程序在fork返回之前在子进程环境中调用。


可以多次调用pthread_atfork函数从而设置多套fork处理程序。如果不需要使用其中某个处理程序,可以给特定的处理程序

参数传入空指针,这样就不会起任何作用作用。使用多个fork处理程序时,处理程序的调用顺序并不相同。parent和child

处理程序时以它们注册时的顺序进行调用的。而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 <pthread.h>
#include <stdio.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void child(void){
        pthread_mutex_unlock(&mutex);
}

void* th_fn(void* arg) {
        printf("start th_fn.\n");
        pthread_mutex_lock(&mutex);
        printf("in th_fn.\n");
        sleep(5);
        pthread_mutex_unlock(&mutex);
        printf("end th_fn.\n");
        return 0;
}

void ps_fn(){
        printf("start ps_fn.\n");
        pthread_mutex_lock(&mutex);
        printf("in ps_fn.\n");
        sleep(5);
        pthread_mutex_unlock(&mutex);
        printf("end ps_fn.\n");
}

int main(void) {
        pthread_t t;

        pthread_atfork(NULL,NULL,child);

        pthread_create(&t, 0, th_fn, 0);
        sleep(2);
        if (fork() == 0) {
              ps_fn();
              return 0;
        }
        pthread_join(t, 0);
        while(1){
        sleep(1);
        }
}
运行结果:

yan@yan-vm:~/apue$ ./a.out
start th_fn.
in th_fn.
start ps_fn.
in ps_fn.
end th_fn.
end ps_fn.

你可能感兴趣的:(《UNIX环境高级编程》笔记--线程和fork)