多线程中fork与mutex
在多线程程序中fork出一个新进程,发现新的进程无法正常工作。因为:在使用fork时会将原来进程中的所有内存数据复制一份保存在子进程中。但是在拷贝的时候,但是线程是无法被拷贝的。如果在原来线程中加了锁,在使用的时候会造成死锁。可以将开线程的代码放在fork以后。也就是放在新的子进程中进行创建。
在多线程程序里,在”自身以外的线程存在的状态”下一使用fork的话,就可能引起各种各样的问题.比较典型的例子就是,fork出来的子进程可能会死锁.请不要,在不能把握问题的原委的情况下就在多线程程序里fork子进程.
在子进程的执行开始处调用doit()时,发生死锁的机率会很高.
void* doit(void*) { static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_lock(&mutex); , }; nanosleep(&ts, ); // // 睡10秒 pthread_mutex_unlock(&mutex); ; } int main(void) { pthread_t t; pthread_create(&t, , doit, ); // 做成并启动子线程 ) { //子进程 //在子进程被创建的瞬间,父的子进程在执行nanosleep的场合比较多 doit(); ; } pthread_join(t, ); // 等待子线程结束 }
以下是说明死锁的理由:
一般的,fork做如下事情
- 父进程的内存数据会原封不动的拷贝到子进程中
- 子进程在单线程状态下被生成
在内存区域里,静态变量mutex的内存会被拷贝到子进程里.而且,父进程里即使存在多个线程,但它们也不会被继承到子进程里. fork的这两个特征就是造成死锁的原因.
- 线程里的doit()先执行.
- doit执行的时候会给互斥体变量mutex加锁.
- mutex变量的内容会原样拷贝到fork出来的子进程中(在此之前,mutex变量的内容已经被线程改写成锁定状态).
- 子进程再次调用doit的时候,在锁定互斥体mutex的时候会发现它已经被加锁,所以就一直等待,直到拥有该互斥体的进程释放它(实际上没有人拥有这个mutex锁).
- 线程的doit执行完成之前会把自己的mutex释放,但这是的mutex和子进程里的mutex已经是两份内存.所以即使释放了mutex锁也不会对子进程里的mutex造成什么影响.
以上程序执行流程
- 在fork前的父进程中,启动了线程1和2
- 线程1调用doit函数
- doit函数锁定自己的mutex
- 线程1执行nanosleep函数睡10秒
- 在这儿程序处理切换到线程2
- 线程2调用fork函数
- 生成子进程
- 这时,子进程的doit函数用的mutex处于”锁定状态”,而且,解除锁定的线程在子进程里不存在
- 子进程的处理开始
- 子进程调用doit函数
- 子进程再次锁定已经是被锁定状态的mutex,然后就造成死锁
像这里的doit函数那样的,在多线程里因为fork而引起问题的函数,我们把它叫 做”fork-unsafe函数”.反之,不能引起问题的函数叫做”fork-safe函数”.虽然在一些商用的UNIX里,源于OS提供的函数(系统调 用),在文档里有fork-safety的记载,但是在 Linux(glibc)里当然!不会被记载.即使在POSIX里也没有特别的规定,所以那些函数是fork-safe的,几乎不能判别.不明白的话,作 为unsafe考虑的话会比较好一点吧.(2004/9/12追记)Wolfram Gloger说过,调用异步信号安全函数是规格标准,所以试着调查了一下,在pthread_atforkの这个地方里有” In the meantime*5, only a short list of async-signal-safe library routines are promised to be available.”这样的话.好像就是这样.
malloc函数就是一个维持自身固有mutex的典型例子,通常情况下它是fork-unsafe的.依赖于malloc函数的函数有很多,例如printf函数等,也是变成fork-unsafe的.
直目前为止,已经写上了thread+fork是危险的,但是有一个特例需要告诉大家.”fork后马上调用exec的场合,是作为一个特列不会产生问题的”. 什么原因呢..?exec函数*6一被调用,进程的”内存数据”就被临时重置成非常漂亮的状态.因此,即使在多线程状态的进程里,fork后不马上调用一切危险的函数,只是调用exec函数的话,子进程将不会产生任何的误动作.但是,请注意这里使用的”马上”这个词.即使exec前仅仅只是调用一回printf(“I’m child process”),也会有死锁的危险.
译者注:exec函数里指明的命令一被执行,该命令的内存映像就会覆盖父进程的内存空间.所以,父进程里的任何数据将不复存在.
本blog的理解:查看前面进程创建中,子进程在创建后,是写时复制的,也就是子进程刚创建时,与父进程一样的副本,当exce后,那么老的地址空间被丢弃,而被新的exec的命令的内存的印像覆盖了进程的内存空间,所以锁的状态无关紧要了。
规避方法1:做fork的时候,在它之前让其他的线程完全终止.
在fork之前,让其他的线程完全终止的话,则不会引起问题.但这仅仅是可能的情况.还有,因为一些原因而其他线程不能结束就执行了fork的时候,就会是产生出一些解析困难的不具合的问题.
规避方法2:fork后在子进程中马上调用exec函数
不用使用规避方法1的时候,在fork后不调用任何函数(printf等)就马上调用execl等,exec系列的函数.如果在程序里不使用”没有exec就fork”的话,这应该就是实际的规避方法吧.把原本子进程应该做的事情写成一个单独的程序,编译成可执行程序后由exec函数来调用.
规避方法3:”其他线程”中,不做fork-unsafe的处理
除了调用fork的线程,其他的所有线程不要做 fork-unsafe的处理.为了提高数值计算的速度而使用线程的场合*7,这可能是fork- safe的处理,但是在一般的应用程序里则不是这样的.即使仅仅是把握了那些函数是fork-safe的,做起来还不是很容易的.fork-safe函 数,必须是异步信号安全函数,而他们都是能数的过来的.因此,malloc/new,printf这些函数是不能使用的.
规避方法4:使用pthread_atfork函数,在即将fork之前调用事先准备的回调函数.apue中详细介绍了它
使用pthread_atfork函数,在即将 fork之前调用事先准备的回调函数,在这个回调函数内,协商清除进程的内存数据.但是关于OS提供的函数 (例:malloc),在回调函数里没有清除它的方法.因为malloc里使用的数据结构在外部是看不见的.因此,pthread_atfork函数几乎 是没有什么实用价值的.
规避方法5:在多线程程序里,不使用fork
就是不使用fork的方法.即用pthread_create来代替fork.这跟规避策2一样都是比较实际的方法,值得推荐.
- 生成子进程的系统调用
- 全局变量和函数内的静态变量
- 如果使用Linux的话,查看pthread_atfork函数的man手册比较好.关于这些流程都有一些解释.
- Solaris和HP-UX等
- 从fork后到exec执行的这段时间
- execve系统调用
- 仅仅做四则演算的话就是fork-safe的
总结:在程序正常运行时出现不能正常工作,而在调试时又能正常工作。则可以考虑死锁的情况!
多线程中fork与mutex的更多相关文章
- 谨慎使用多线程中的fork 学习!!!!
前言 在单核时代,大家所编写的程序都是单进程/单线程程序.随着计算机硬件技术的发展,进入了多核时代后,为了降低响应时间,重复充分利用多核cpu的资源,使用多进程编程的手段逐渐被人们接受和掌握.然而因为 ...
- 多线程程序中fork导致的一些问题
最近项目中,在使用多线程和多进程时,遇到了些问题. 问题描述:在多线程程序中fork出一个新进程,发现新的进程无法正常工作. 解决办法:将开线程的代码放在fork以后.也就是放在新的子进程中进行创建. ...
- python中fork()函数生成子进程分析
python的os module中有fork()函数用于生成子进程,生成的子进程是父进程的镜像,但是它们有各自的地址空间,子进程复制一份父进程内存给自己,两个进程之 间的执行是相互独立的,其执行顺序可 ...
- c#语言-多线程中的锁系统(一)
介绍 平常在多线程开发中,总避免不了线程同步.本篇就对net多线程中的锁系统做个简单描述. 目录 一:lock.Monitor 1:基础. 2: 作用域. ...
- Java 多线程中的任务分解机制-ForkJoinPool,以及CompletableFuture
ForkJoinPool的优势在于,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行:当多个“小任务”执行完成之后,再将这些执行结果 ...
- python 多线程中的同步锁 Lock Rlock Semaphore Event Conditio
摘要:在使用多线程的应用下,如何保证线程安全,以及线程之间的同步,或者访问共享变量等问题是十分棘手的问题,也是使用多线程下面临的问题,如果处理不好,会带来较严重的后果,使用python多线程中提供Lo ...
- python中fork()函数生成子进程分析-乾颐堂
python的os module中有fork()函数用于生成子进程,生成的子进程是父进程的镜像,但是它们有各自的地址空间,子进程复制一份父进程内存给自己,两个进程之 间的执行是相互独立的,其执行顺序可 ...
- 【转】linux 中fork()函数详解
在看多线程的时候看到了这个函数,于是学习了下,下面文章写的通俗易懂,于是就开心的看完了,最后还是很愉快的算出了他最后一个问题. linux 中fork()函数详解 一.fork入门知识 一个进程,包括 ...
- localtime死锁——多线程下fork子进程
近期測试我们自己改进的redis,发如今做rdb时,子进程会一直hang住.gdb attach上.堆栈例如以下: (gdb) bt #0 0x0000003f6d4f805e in __lll_lo ...
随机推荐
- 使用python发邮件:
import smtplibfrom email.mime.text import MIMETextfrom email.utils import formataddr#定义发送的内容:msg = M ...
- Koa 中间件的执行顺序
中间件工作原理 初始化koa实例后,我们会用use方法来加载中间件(middleware),会有一个数组来存储中间件,use调用顺序会决定中间件的执行顺序. 每个中间件都是一个函数(不是函数将报错), ...
- Vue 变异方法Push的留言板实例
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Codeforces Round #596 (Div. 2, based on Technocup 2020 Elimination Round 2) E. Rock Is Push dp
E. Rock Is Push You are at the top left cell (1,1) of an n×m labyrinth. Your goal is to get to the b ...
- Windows Azure Virtual Machine (38) 跨租户迁移使用托管磁盘的Azure虚拟机
<Windows Azure Platform 系列文章目录> 背景介绍: (1)我们建议使用Azure Manage Disk托管磁盘来创建Azure虚拟机 (2)使用托管磁盘的好处是, ...
- eclipse 下载、安装、创建java文件工程、运行---Windows 10
一.Eclipse Eclipse 是一个开放源代码的.基于Java的可扩展开发平台.就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境.幸运的是,Eclipse 附带了一个标准的插 ...
- 【MySQL】Mariadb主从复制
Mariadb主从复制 环境配置: Master : 172.30.200.200 Slave : 172.30.200.204 架构图,如下: Master的配置: 1.binlog配置 [root ...
- Kubernetes Pod 资源限制
Kubernetes Pod 资源限制 官方文档:https://kubernetes.io/docs/concepts/configuration/manage-compute-resources- ...
- Could not find resource——mybatis 找不到映射器xml文件
今天用IDEA写Mybatis的时候,测试报了如图所示的错,恶心死我了,后来解决了,总结一下,防止下回跳坑,当然,也是做一个分享,如果有朋友遇到这个错,希望有所帮助 Error parsing SQL ...
- elasticSearch查询(一)
**整理成sql格式来看懂elastic** 1.多个字段多个and查询 sql格式:select * from product where title = 'xxxx' and pid = 12 l ...