当线程调用fork时,就为子进程创建了整个进程地址空间的副本。回忆http://www.cnblogs.com/nufangrensheng/p/3509492.html中讨论的写时复制,子进程与父进程是完全不同的进程,只要两者都没有对内存作出改动,父进程和子进程之间还可以共享内存页的副本。

子进程通过继承整个地址空间的副本,也从父进程那里继承了所有互斥量、读写锁和条件变量的状态如果父进程包含多个线程,子进程在fork返回以后,如果紧接着不是马上调用exec的话,就需要清理锁状态。

在子进程内部只存在一个线程,它是由父进程中调用fork的线程的副本构成的。如果父进程中的线程占有锁,子进程同样占有这些锁。问题是子进程并不包含占有锁的线程的副本,所以子进程没有办法知道它占有了哪些锁并且需要释放哪些锁。

如果子进程从fork返回以后马上调用某个exec函数,就可以避免这样的问题。这种情况下,老的地址空间被丢弃,所以锁的状态无关紧要。但如果子进程需要继续做处理工作的话,这种方法就行不通,还需要使用其他的策略。

要清除锁状态,可以通过调用pthread_atfork函数建立fork处理程序。

#include <pthread.h>
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));
返回值:若成功则返回0,否则返回错误编号

用pthread_atfork函数最多可以安装三个帮助清理锁的函数。prepare fork处理程序由父进程在fork创建子进程前调用,这个fork处理程序的任务是获取父进程定义的所有锁。parent fork处理程序是在fork创建了子进程以后,但在fork返回之前在父进程环境中调用的,这个fork处理程序的任务是对prepare fork处理程序获得的所有锁进行解锁。child fork处理程序在fork返回之前在子进程环境中调用,与parent fork处理程序一样,child fork处理程序也必须释放prepare fork处理程序获得的所有锁。

注意不会出现加锁一次解锁两次的情况,虽然看起来也许会出现。当子进程地址空间创建的时候,它得到了父进程定义的所有锁的副本。因为prepare fork处理程序获取所有的锁,父进程中的内存和子进程中的内存内容在开始的时候是相同的。当父进程和子进程对他们的锁的副本进行解锁的时候,新的内存是分配给子进程的,父进程的内存内容被复制到子进程的内存中(写时复制),所以就会陷入这样的假象,看起来父进程对它所有的副本进行了加锁,子进程对它所有的副本进行了加锁。父进程和子进程对在不同内存位置的重复的锁都进行了解锁操作,就好像出现了下列的时间序列:

(1)父进程获得所有的锁。

(2)子进程获得所有的锁。

(3)父进程释放它的锁。

(4)子进程释放它的锁。

可以多次调用pthread_atfork函数从而设置多套fork处理程序。如果不需要使用其中某个处理程序,可以给特定的处理程序参数传入空指针,这样它们就不会起任何作用。使用多个fork处理程序时,处理程序的调用顺序并不相同。parentchild fork处理程序是以它们注册时的顺序进行调用的,而prepare fork处理程序的调用顺序与它们注册时的顺序相反。这样可以允许多个模块注册它们自己的fork处理程序,并且保持锁的层次。

例如,假设模块A调用模块B中的函数,而且每个模块有自己的一套锁。如果锁的层次是A在B之前,模块B必须在模块A之前设置fork处理程序。当父进程调用fork时,就会执行以下的步骤,假设子进程在父进程之前运行。

(1)调用模块A的prepare fork处理程序获取模块A的所有锁。

(2)调用模块B的prepare fork处理程序获取模块B的所有锁。

(3)创建子进程。

(4)调用模块B中的child fork处理程序释放子进程中模块B的所有锁。

(5)调用模块A中的child fork处理程序释放子进程中模块A的所有锁。

(6)fork函数返回到子进程。

(7)调用模块B中的parent fork处理程序释放父进程中模块B的所有锁。

(8)调用模块A中的parent fork处理程序释放父进程中模块A的所有锁。

(9)fork函数返回到父进程。

如果fork处理程序是为了清理锁状态,那么又由谁来负责清理条件变量的状态呢?在有些操作系统的实现中,条件变量可能并不需要做任何清理。但是有些操作系统实现把锁作为条件变量实现的一部分,这种情况下的条件变量就需要清理。问题是目前不存在这样的接口,如果锁是嵌入到条件变量的数据结构中的,那么在调用fork之后就不能使用条件变量,因为还没有可移植的方法对其进行状态清理。另外,如果操作系统的实现是使用全局锁保护进程中所有的条件变量数据结构,那么操作系统实现本身可以在fork库例程中做清理锁的工作,但是应用程序不应该依赖操作系统实现中这样的细节。

实例

程序清单12-7中的程序描述了如何使用pthread_atfork和fork处理程序。

程序清单12-7 pthread_atfork实例

#include "apue.h"
#include <pthread.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 *
thr_fn(void *arg)
{
printf("thread started...\n");
pause();
return(0);
} int
main(void)
{
int err;
pid_t pid;
pthread_t tid; #if defined(BSD) || defined(MACOS)
printf("pthread_atfork is unsupported\n");
#else
if((err = pthread_atfork(prepare, parent, child)) != 0)
err_exit(err, "can't install fork handlers");
err = pthread_create(&tid, NULL, thr_fn, 0);
if(err != 0)
err_exit(err, "can't create thread");
sleep(2);
printf("parent about to fork...\n");
if((pid = fork()) < 0)
err_quit("fork failed");
else if(pid == 0) /* child */
printf("child returned from fork\n");
else /* parent */
printf("parent returned from fork\n");
#endif
exit(0);
}

程序中定义了两个互斥量,lock1和lock2,prepare fork处理程序获取这两把锁,child fork处理程序在子进程环境中释放锁,parent fork处理程序在父进程中释放锁。

运行该程序,得到如下输出:

可以看出,prepare fork处理程序在调用fork以后运行,child fork处理程序在fork调用返回到子进程之前运行,parent fork处理程序在fork调用返回给父进程前运行。

本篇博文内容摘自《UNIX环境高级编程》(第二版),仅作个人学习记录所用。关于本书可参考:http://www.apuebook.com/

线程控制之线程和fork的更多相关文章

  1. 线程锁的本质:线程控制、线程状态控制 while if:根据线程的关系(模式)协调线程的执行

    线程锁的本质:线程控制.线程状态控制 while if https://www.cnblogs.com/feng9exe/p/8319000.html https://www.cnblogs.com/ ...

  2. 0039 Java学习笔记-多线程-线程控制、线程组

    join线程 假如A线程要B线程去完成一项任务,在B线程完成返回之前,不进行下一步执行,那么就可以调用B线程的join()方法 join()方法的重载: join():等待不限时间 join(long ...

  3. Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信

    1.Java使用Thread类代表线程.     所有的线程对象必须是Thread类或其子类的实例. 当线程继承Thread类时,直接使用this即可获取当前线程,Thread对象的getName() ...

  4. 线程控制之线程和I/O

    http://www.cnblogs.com/nufangrensheng/p/3498723.html中介绍了pread和pwrite函数,这些函数在多线程环境下是非常有帮助的,因为进程中的所有线程 ...

  5. linux线程控制-2(线程控制函数)

    记录肖堃老师讲解的linux线程 1. 创建线程 int pthread_create( (pthread_t *thread, pthread_attr_t *attr, void *(*start ...

  6. 第10章 线程控制(5)_多线程下的fork

    6. 线程和fork 6.1 多线程下的fork (1)历史包袱 ①fork与多线程的协作性很差,这是POSIX系统操作系统的历史包袱. ②长期以来程序都是单线程的,fork运行正常,但引入线程这后, ...

  7. (十) 一起学 Unix 环境高级编程 (APUE) 之 线程控制

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

  8. Unix环境高级编程(十二)线程控制

    本章介绍了一个进程中多个线程之间如何保持数据的似有性及进程的系统调用如何与线程进行交互. 1.线程限制: Single Unix定义了一线线程操作的限制,和其他的限制一样,可以通过sysconf来查询 ...

  9. apue学习笔记(第十二章 线程控制)

    本章将讲解控制线程行为方面的详细内容,而前面的章节中使用的都是它们的默认行为 线程属性 pthread接口允许我们通过设置每个对象关联的不同属性来细调线程和同步对象的行为.管理这些属性的函数都遵循相同 ...

随机推荐

  1. <转>写给浮躁的IT同仁(请不要做浮躁的人)

    1.不要看到别人的回复第一句话就说:给个代码吧!你应该想想为什么.当你自己想出来再参考别人的提示,你就知道自己和别人思路的差异. 2.初学者请不要看太多太多的书那会误人子弟的,先找本系统的学,很多人用 ...

  2. 修复duilib库UISlider控件的4个bug

    转载级请注明原出处,谢谢~· 昨天封装好一个音频类,我在为dulib做音频播放demo时发现了一些问题,由CSliderUI控件导致的,进而发现了这个控件的好几样不足,他无法满 足我们做一个播放器的进 ...

  3. JS代码的简单重构与优化(适合新手)

    Demo . 1 //bad if (age > 20) { return true; } else { return false; } //good return age > 20; 这 ...

  4. ORA-15041: diskgroup space exhausted

    今天在做一个备份的时候,出现磁盘耗尽的错误,具体如下: RMAN-00571: =========================================================== ...

  5. 七牛云存储官方接口PHP版本

    PHP SDKv6 此 SDK 适用于 PHP 5.1.0 及其以上版本.基于 七牛云存储官方API 构建.使用此 SDK 构建您的网络应用程序,能让您以非常便捷地方式将数据安全地存储到七牛云存储上. ...

  6. Hadoop MapReduce程序中解决第三方jar包问题方案

    hadoop怎样提交多个第三方jar包? 方案1:把所有的第三方jar和自己的class打成一个大的jar包,这种方案显然笨拙,而且更新升级比较繁琐. 方案2: 在你的project里面建立一个lib ...

  7. 06 java中常量以及常量池

    1.举例说明 变量 常量 字面量 int a=10; float b=1.234f; String c="abc"; final long d=10L; a,b,c为变量,d为常量 ...

  8. nginx 502 错误

    今天帮朋友处理一个程序报错,重启nginx服务之后,发现首页打不开了,但是静态文件可以打开 经检查nginx 服务器正常运行,重启无数次仍然502错误,考虑到静态文件可以打开,怀疑可能是php 脚本程 ...

  9. 单片机C语言下LCD多级菜单的一种实现方法

    摘要:     介绍了在C 语言环境下,在LCD 液晶显示屏上实现多级嵌套菜单的一种简便方法,提出了一个结构紧凑.实用的程序模型. 关键词: 液晶显示屏; 多级菜单; 单片机; C 语言; LCD 中 ...

  10. Java 8 正式发布,新特性全搜罗

    经过2年半的努力.屡次的延期和9个里程碑版本,甲骨文的Java开发团队终于发布了Java 8正式版本. Java 8版本最大的改进就是Lambda表达式,其目的是使Java更易于为多核处理器编写代码: ...