前言
有关进程、多线程、fork的概念,请看我之前写的这两篇文章。
Linux:进程控制(进程创建、进程终止、进程等待、进程程序替换)
Linux:详解多线程(线程概念、线程控制—线程创建、线程终止、线程等待)(一)
1. 浅谈在多线程下的fork的问题
当fork函数创建出一个子进程的时候,子进程会拷贝父进程的进程虚拟地址空间,并且也会从父进程中拷贝一份相应的内存数据到子进程中,这是我们所知道的在单进程的情况下它是这样的,但是如果是在多线程中呢?在多线程代码中,如果在某个工作线程中调用fork函数时,fork函数是怎样运作的?是将该程序的整个工作线程都拷贝一份呢,还是只是拷贝当前创建出他的工作线程的虚拟地址空间?
在 POSIX 标准中,fork 的行为是这样的:复制整个用户空间的数据(通常使用 copy-on-write 的策略,所以可以实现的速度很快)以及所有系统对象, 然后仅复制当前线程到子进程。这里:所有父进程中别的线程,到了子进程中都是突然蒸发掉的。其它线程的突然消失,是一切问题的根源。
在大多数操作系统上,为了性能的因素,锁基本上都是实现在用户态的而非内核态(因为在用户态实现最方便,基本上就是通过原子操作),所以调用fork的时候,会复制父进程的所有锁到子进程中。
这就造成了,如果线程A在创建子进程之前就有其他线程对当前全局变量中的锁进行了加锁操作(换句话说就是在创建子进程之前对应的锁就已经被拿到了),当创建出子进程之后,子进程和对应父进程就是两个完全不同的进程(进程独立性),但是子进程却是拷贝于父进程的进程虚拟空间,这就造成了如果父进程中已经拿到了锁,子进程中所对应的那把锁也是被加锁状态,但是子进程目前是不知道这把锁的状态的,因此,一旦在子进程中对这把锁进行加锁操作,就会造成死锁的现象产生。
总结一下,出现死锁的原因就是:
- 父进程的内存数据会原封不动的拷贝到子进程中
- 子进程在单线程状态下被生成,仅生成fork函数所在的这个线程。
- 父进程(在创建子进程之前)中的某个锁已经被lock掉了,并且子进程中对这把锁再次进行lock,就会发生死锁
2. 死锁问题的模拟实现
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
#include <iostream>
using namespace std;
pthread_mutex_t mt;
void* pthreadFork(void * arg)
{
pthread_detach(pthread_self());
//等待2秒的原因是为了先让pthreadLock拿到锁
sleep(2);
int ret = fork();
if(ret < 0)
{
cout << "fork failed" << endl;
return 0;
}
else if(ret == 0)
{
//child
cout << "It's Child !" << endl;
while(1)
{
pthread_mutex_lock(&mt);
cout << "It's test pthreadFork_Child "