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

我们先来看一个程序:

[cpp]  view plain  copy
  1. #include <pthread.h>  
  2. #include <stdio.h>  
  3. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  
  4.   
  5. void* th_fn(void* arg) {  
  6.         printf("start th_fn.\n");  
  7.         pthread_mutex_lock(&mutex);  
  8.         printf("in th_fn.\n");  
  9.         sleep(5);  
  10.         pthread_mutex_unlock(&mutex);  
  11.         printf("end th_fn.\n");  
  12.         return 0;  
  13. }  
  14.   
  15. void ps_fn(){  
  16.         printf("start ps_fn.\n");  
  17.         pthread_mutex_lock(&mutex);  
  18.         printf("in ps_fn.\n");  
  19.         sleep(5);  
  20.         pthread_mutex_unlock(&mutex);  
  21.         printf("end ps_fn.\n");  
  22. }  
  23.   
  24. int main(void) {  
  25.         pthread_t t;  
  26.         pthread_create(&t, 0, th_fn, 0);  
  27.         sleep(2);  
  28.         if (fork() == 0) {  
  29.               ps_fn();  
  30.               return 0;  
  31.         }  
  32.         pthread_join(t, 0);  
  33.         while(1){  
  34.         sleep(1);  
  35.         }  
  36. }  

执行结果:

# ./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函数。

[cpp]  view plain  copy
  1. #include<pthread.h>  
  2. int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));  
  3. //成功则返回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函数返回到父进程。


下面我们修改下程序:

[cpp]  view plain  copy
  1. #include <pthread.h>  
  2. #include <stdio.h>  
  3. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  
  4.   
  5. void child(void){  
  6.         pthread_mutex_unlock(&mutex);  
  7. }  
  8.   
  9. void* th_fn(void* arg) {  
  10.         printf("start th_fn.\n");  
  11.         pthread_mutex_lock(&mutex);  
  12.         printf("in th_fn.\n");  
  13.         sleep(5);  
  14.         pthread_mutex_unlock(&mutex);  
  15.         printf("end th_fn.\n");  
  16.         return 0;  
  17. }  
  18.   
  19. void ps_fn(){  
  20.         printf("start ps_fn.\n");  
  21.         pthread_mutex_lock(&mutex);  
  22.         printf("in ps_fn.\n");  
  23.         sleep(5);  
  24.         pthread_mutex_unlock(&mutex);  
  25.         printf("end ps_fn.\n");  
  26. }  
  27.   
  28. int main(void) {  
  29.         pthread_t t;  
  30.   
  31.         pthread_atfork(NULL,NULL,child);  
  32.   
  33.         pthread_create(&t, 0, th_fn, 0);  
  34.         sleep(2);  
  35.         if (fork() == 0) {  
  36.               ps_fn();  
  37.               return 0;  
  38.         }  
  39.         pthread_join(t, 0);  
  40.         while(1){  
  41.         sleep(1);  
  42.         }  
  43. }  
运行结果:

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值