`

线程和 fork

阅读更多
    父进程调用 fork 创建的子进程会继承整个地址空间的副本,以及每个互斥量、读写锁和条件变量的状态。如果父进程包含一个以上的线程,子进程在 fork 返回后,如果不是紧接着调用 exec 的话,就需要清理锁状态。因为在子进程内部,只存在父进程中调用 fork 的线程的副本一个线程。如果父进程中的线程占有锁,子进程将同样占有这些锁。问题是子进程可能并不包含占有锁的线程的副本,所以它就没法知道它占有了哪些锁、需要释放哪些锁。
    因此在多线程的进程中,为了避免不一致状态的问题,POSIX.1 声明,在 fork 返回和子进程调用 exec 族函数之间,子进程只能调用异步信号安全的函数。这就限制了在调用 exec 之前子进程能做什么,但不涉及锁状态的问题。要清除锁状态,可以调用 pthread_atfork 函数建立 fork 处理程序(对于条件变量,由于它可能是使用全局锁来保护,也可能是直接把锁嵌入到条件变量的数据结构中,目前还没有可移植的方法对这样的锁进行状态清理)。
#include <pthread.h>
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));
                                  /* 返回值:若成功,返回 0;否则,返回错误编号 */

    使用该函数一次最多可以安装 3 个清理锁的函数。其中 prepare 函数由父进程在 fork 创建子进程前调用,用来获取父进程定义的所有锁。parent 函数是在 fork 创建子进程之后、返回之前在父进程上下文中调用的,用来对 prepare 获取的所有锁进行解锁。child 函数则是在 fork 返回之前在子进程上下文中调用,用来释放 prepare 获取的所有锁。
    可以多次调用该函数来设置多套 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 函数返回到父进程。
    下面的程序描述了如何使用 pthread_atfork 和 fork 处理程序。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

pthread_mutex_t	lock1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER;

void prepare(void){
	printf("prepare acquiring 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();
	printf("thread ended\n");  // can't reach here, for no signal handlers.
	return (void *)0;
}

int main(void){
	if(pthread_atfork(prepare, parent, child) != 0){
		printf("pthread_atfork error\n");
		exit(1);
	}
	pthread_t	tid;
	if(pthread_create(&tid, NULL, thr_fn, NULL) != 0){
		printf("pthread_create error\n");
		exit(1);
	}
	sleep(2);		// Not reliably waiting for thread to run.
	printf("parent process about to call fork()...\n");
	pid_t	pid;
	if((pid = fork()) < 0){
		printf("fork error\n");
		exit(1);
	}
	if(pid == 0)
		printf("child returned from fork\n");
	else
		printf("parent returned from fork\n");
	exit(0);
}

    运行结果如下:
$ ./atforkDemo.out 
thread started
parent process about to call fork()...
prepare acquiring locks
parent unlocking locks
parent returned from fork
child unlocking locks
child returned from fork

    由此可见 prepare 处理程序在调用 fork 之后运行,child 在调用返回到子进程之前执行,parent 在 fork 调用返回给父进程之前运行。
    虽然 pthread_atfork 的意图是使 fork 之后的锁状态保持一致,但它还是存在下面这些不足之处,因此只能在有限情况下可用。
   (1)没有很好的办法对较复杂的同步对象(如条件变量和屏障)进行状态的重新初始化。
   (2)某些错误检查的互斥量实现在 child fork 处理程序试图对被父进程加锁的互斥量进行解锁时会产生错误。
   (3)递归互斥量不能在 child fork 处理程序中清理,因为没法确定加锁的次数。
   (4)如果子进程只允许调用异步信号安全的函数,child fork 处理程序就不可能清理同步对象,因为用于操作清理的所有函数都不是异步信号安全的。实际的问题是同步对象在某个线程调用 fork 时可能处于中间状态,除非同步对象处于一致状态,否则无法被清理。
   (5)如果应用程序在信号处理程序中调用了 fork(这是合法的,因为 fork 本身是异步信号安全的),则 pthread_atfork 注册的 fork 处理程序只能调用异步信号安全的函数,否则结果将是未定义的。
分享到:
评论

相关推荐

    linux 进程 线程 fork 的深入思考

    ### Linux 下进程、线程与 fork 的深入理解 #### 题目背景及解析 本篇文章将基于一道经典的面试题目来探讨 Linux 下进程创建机制,特别是 `fork` 函数的工作原理。该题目不仅考验应试者对进程创建的理解,还涉及了...

    Unix环境高级编程——线程控制PPT

    9. **线程和fork**:`fork`创建子进程时,子进程继承父进程的所有线程,但通常只执行`fork`后的那个线程,其他线程的状态不确定,需谨慎处理。 以上内容涵盖了Unix环境下线程控制的主要方面,包括基本操作、属性...

    LINUX环境高级编程 第六章 线程管理

    线程的概念 线程的创建 线程的终止 线程的同步 线程属性 同步属性 取消选项 线程和信号 线程和fork

    Python在UNIX和Linux系统管理指南 中文版

    ·使用多线程和fork选项。 ·使用网络设备从一个进程获取另一个进程的信息。 ·创建可点击的、易于交互的GUI工具。 ·通过交互式SNMP编程实现监控大型主机集群。 ·掌握IPython shell,作为Bash、Korn或Z-Shell的...

    Python.Unix和Linux系统管理指南

    使用多线程和fork选项。 使用网络设备从一个进程获取另一个进程的信息。 创建可点击的、易于交互的GUl工具。 通过交互式SNMP编程实现监控大型主机集群。 掌握IPython shell,作为Bash、Korn或Z—Shell的替换或补充。...

    《Python UNIX 和Linux 系统管理指南》[PDF]

    ·使用多线程和fork选项。 ·使用网络设备从一个进程获取另一个进程的信息。 ·创建可点击的、易于交互的gui工具。 ·通过交互式snmp编程实现监控大型主机集群。 ·掌握ipython shell,作为bash、korn或z-shell的...

    Python.Unix和Linux系统管理指南 pdf

    ·使用多线程和fork选项。 ·使用网络设备从一个进程获取另一个进程的信息。 ·创建可点击的、易于交互的gui工具。 ·通过交互式snmp编程实现监控大型主机集群。 ·掌握ipython shell,作为bash、korn或z-shell的...

    PYTHON UNIX和LINUX系统管理指南

    , 使用多线程和fork选项。, 使用网络设备从一个进程获取另一个进程的信息。, 创建可点击的、易于交互的GUl工具。, 通过交互式SNMP编程实现监控大型主机集群。, 掌握IPython shell,作为Bash、Korn或Z—Shell的替换或...

    并行计算课程设计报告.pdf

    Java的并发库提供了线程和Fork/Join框架,适合进行更复杂的并行任务管理。 3. **设计分析** - **串行算法设计**:首先,学生需要设计和实现一个基础的串行算法,作为并行版本的基准,用于后续的性能比较。 - **...

    APUE读书笔记(Unix高级环境编程)

    - **线程和fork**:探讨在多线程环境中调用`fork`函数的影响。 - **线程和I/O**:在线程中进行I/O操作时应注意的问题。 #### 第十三章 守护进程 - **设计良好的守护进程的规则**:包括脱离终端、成为会话领导等...

    UNIX环境高级编程_第二版中文

    12.9 线程和fork  12.10 线程和I/O  12.11 小结  习题  第13章 守护进程  13.1 引言  13.2 守护进程的特征  13.3 编程规则  13.4 出错记录  13.5 单实例守护进程  13.6 守护进程的惯例  13.7...

    我的APUE2读书笔记

    线程和fork** `fork`函数在多线程环境下需要注意同步问题。 **8. 线程和I/O** 线程安全的I/O函数。 #### 第十三章 守护进程 **1. 设计一个良好的守护进程的一般编程规则** 守护进程需要脱离控制终端,忽略大多数...

    Nachos的线程管理模块升级

    在Nachos中,通过添加新的线程状态`STATIC_READY`和`STATIC_BLOCKED`,以及`Thread::suspend()`和`Thread::active()`方法来实现线程的挂起和激活。挂起线程时,根据线程当前状态将其状态改为`STATIC_READY`或`STATIC...

    java Fork Join框架及使用

    Fork/Join框架特别适合处理可以递归拆分的计算密集型任务,比如大数据集的搜索、排序和其他计算。 首先,Fork/Join框架的基本操作包括“fork”和“join”两个动作。fork操作是指将一个大任务拆分为多个可以并行处理...

    APUE读书笔记《UNIX环境高级编程第二版》

    线程和fork** - `fork()`在多线程环境中可能导致问题。 **8. 线程和I/O** - 线程间的I/O操作需要注意同步。 #### 第十三章 守护进程 **1. 设计一个良好的守护进程的一般编程规则** - 守护进程应该脱离终端,重新...

    UNIX环境高级编程(第二版中文)

    12.9 线程和fork 336 12.10 线程和I/O 339 12.11 小结 340 习题 340 第13章 守护进程 341 13.1 引言 341 13.2 守护进程的特征 341 13.3 编程规则 342 13.4 出错记录 345 13.5 单实例守护进程 348...

    unix环境编程电子书

    313 12.3 线程属性 314 12.4 同步属性 318 12.5 重入 324 12.6 线程私有数据 328 12.7 取消选项 331 12.8 线程和信号 333 12.9 线程和fork 336 12.10 线程和I/O 339 12.11 小结 340 习题 340 第...

    gdb官方文档

    文档内容涵盖了如何使用GDB进行源代码级调试的各个方面,包括但不限于如何启动和退出GDB,如何在GDB中运行程序,如何使用断点、观察点和捕获点,以及如何在GDB中处理多进程、多线程和fork的调试。这些内容对于开发者...

    UNIX环境高级编程_第2版.part1

    12.9 线程和fork 336 12.10 线程和i/o 339 12.11 小结340 习题340 第13章守护进程341 13.1 引言341 13.2 守护进程的特征341 13.3 编程规则342 13.4 出错记录345 13.5 单实例守护进程348 13.6 守护进程的...

Global site tag (gtag.js) - Google Analytics