谨防fork与锁之间的深坑
来源:互联网 发布:中国最大的网络电商 编辑:程序博客网 时间:2024/04/29 00:37
fork之后应当谨慎使用锁:
这是因为fork有一个特点,那就是子进程只会保留调用fork的那个线程,父进程中其他的线程在子进程中都会消失。但是fork之后,除了文件锁以外,其他的锁都会被继承。这就导致了,如果在子进程中,对某个已经在父进程中加了锁的锁继续加锁,就会导致死锁发生。并且我们无法对该锁进行解锁,因为在子进程中,该锁的持有者并不存在。
下面给一个例子:
#include <stdio.h>#include <unistd.h>#include <pthread.h>#include <sys/types.h>#include <sys/wait.h>pthread_mutex_t mutex;pthread_mutexattr_t attr;void thread_func(void *arg){ pthread_mutex_init(&mutex, &attr); pthread_mutex_lock(&mutex); sleep(10);}int main(){ pthread_t tid; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);//设置互斥锁的类型为PTHREAD_MUTEX_ERRORCHECK。即会严格检查错误。如果不设置该属性,默认属性下解锁一个其他线程占用的锁时产生的行为是未定义的(我自己试了一下,可以解锁成功)。设置了之后,这种情况下就会产生错误EPERM。 pthread_create(&tid, NULL, (void *)thread_func, NULL); int pid; sleep(1); pid = fork(); if(pid == 0) { /* int ret; ret = pthread_mutex_unlock(&mutex); if(ret == EPERM) { printf("don't unlock a lock which not belong to you\n") } 之前我尝试在不设置PTHREAD_MUTEX_ERRORCHECK属性下解锁由thread_func线程占有的锁,是会成功解锁的。当我们设置了该属性之后,便会产生错误值EPERM。 */ pthread_mutex_lock(&mutex); printf("not a deadlock\n"); return 0; } else { waitpid(pid, NULL, 0); } pthread_join(tid, NULL); pthread_mutexattr_destroy(&attr); pthread_mutex_destroy(&mutex); return 0;}
这里再强调一下自己做测试的时候,一定要设置互斥锁的类型,不然你在子进程中尝试解不属于它的锁是会成功的(其实该行为是未定义的,即不知道会发生什么)。。。。
接下来,问题是有了,但是如何解决呢?
系统提供了一个函数 pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void))
,它会在调用fork时自动调用这三个注册的函数。 void (*prepare)(void)
的任务是获取父进程定义的所有锁,由父进程在fork之前调用 void (*parent)(void)
的任务是对prepare
处理程序获取的所有锁进行解锁,在fork创建子进程之后、返回之前的父进程上下文中调用 void (*child)(void)
的任务和parent
处理程序的任务一样,也是对prepare
获取的所有锁进行解锁,在fork创建子进程之后、返回之前的子进程上下文中调用
它的意图是在fork之前,做好锁的清理工作。
例子如下:
#include <stdio.h>#include <pthread.h>#include <unistd.h>pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;void func(void* arg){ pthread_mutex_lock(&mutex); sleep(10); pthread_mutex_unlock(&mutex);}void prepare(void){ pthread_mutex_lock(&mutex);}void parent(void){ pthread_mutex_unlock(&mutex);}void child(void){ pthread_mutex_unlock(&mutex);}int main(void) { pthread_atfork(prepare, parent, child); pthread_t tid; pthread_create(&tid, NULL, (void *)func, NULL); if (fork() == 0) { func(NULL); printf("no deadlock\n"); return 0; } pthread_join(tid, 0); return 0;}
虽然这确实可以解决死锁问题,但是它并不是万能的。如果获取锁的次序有问题,它反而可能会造成死锁。而且它不能对比较复杂的同步对象(比如条件变量或屏障)进行状态的重新初始化等。这些apue中说的比较详细。
最后的结论就是,调用了fork之后,子进程最好马上调用exec
函数。因为调用exec
后,会把原子程序的正文段、数据段、堆、栈替换成新的可执行程序的对应段。由于性能问题,大部分系统的锁是实现在用户空间的,这样的话,所有的锁都不复存在了。
还有一点需要注意,调用exec
之后,原来父进程打开的文件描述符其实是保持打开状态的。我们需要用open
或者fcntl
函数设置O_CLOEXEC
或者FD_CLOEXEC
标志,使得调用exec
之后,关闭打开的文件描述符。
不过也可以利用这一点,来让exec
执行的程序的结果回送到父进程,主要的操作就是使用dup2
,将用于回送数据的描述符复制给STDOUT_FILENO
标准输出。
- 谨防fork与锁之间的深坑
- fork,vfork,clone与pthread_create之间的区别
- fork函数与I/O函数之间的交互关系
- php的又一深坑
- fork()父子进程变量之间的关系与信号的响应
- fork()子进程与父进程之间的文件描述符问题
- fork()子进程与父进程之间的文件描述符问题
- 项目经验之谈——fork与文件(操作)之间的爱恨情仇
- fork 父子进程变量之间的关系
- 微信支付的深坑
- 嵌入式的深坑(1)
- 谨防加速死亡的成功
- 谨防串行的状态报告会
- fork与线程的关系
- fork与vfork的区别
- fork与vfork的区别
- fork 与 vfork 的区别
- fork与vfork的区别
- Less.Html 示例二:以 Less.Html 做视图引擎
- Android7.0中文文档(API)-- SimpleCursorAdapter
- 挑战程序竞赛系列(14):2.6素数
- USB设备的VID与PID
- Ant、Jmeter Jenkins持续集成
- 谨防fork与锁之间的深坑
- CSS 理解盒子模型
- hibernate hql
- 国产 YI Tunnel 收银机器人如何秒杀日本自助收银方案
- PYthon 学习笔记
- 交互打开IE让文字或图片慢慢显示出来
- JVM原理
- myeclipse10安装svn插件
- SOAP和RESTful 框架的 简介、对比和区别