我们在编写服务器程序时,要么使用多线程,要么使用多进程。如果我们在多线程程序中使用了fork(),这会带来很多不必要的麻烦,甚至带来死锁。
示例:
#include <stdio.h>
#include <time.h>
#include <pthread.h>
#include <unistd.h>
#include <iostream>
using namespace std;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* doit(void *arg)
{
cout<<"pid = "<<static_cast<int>(getpid())<<" begin doit"<<endl;
pthread_mutex_lock(&mutex);
struct timespec ts = {3, 0}; //second and nanosecond
nanosleep(&ts, NULL);
pthread_mutex_unlock(&mutex);
cout<<"pid = "<<static_cast<int>(getpid())<<" end doit"<<endl;
return NULL;
}
int main()
{
cout<<"pid = "<<static_cast<int>(getpid())<<" entering main"<<endl;
pthread_t tid;
pthread_create(&tid, NULL, doit, NULL);
struct timespec ts = {1, 0};
nanosleep(&ts, NULL);
if(fork() == 0){
doit(NULL);
}
pthread_join(tid, NULL);
cout<<"pid = "<<static_cast<int>(getpid())<<" end main"<<endl;
return 0;
}
程序解释:
在本程序中我们主线程先创建了一个线程执行doit()函数,在doit函数中对互斥量加锁,此时我们刻意延长该线程持有该锁的时间。然后在主线程中使用fork()函数,子进程再次执行doit()函数。由于进程使用fork()之后,子进程得到与父进程用户级虚拟地址空间相同的但是独立的一份拷贝,包括文本,数据和bss段,堆,以及用户栈等。子进程还获得与父进程任何打开的文件描述符相同的拷贝。重要的是,由于子进程复制了父进程的内存,子进程还会拷贝互斥锁。如果互斥锁处于lock()状态被子进程拷贝,此时子进程中如果给该互斥量上锁,那就会造成死锁。因为子进程fork()了一个lock状态的锁,却没有调用unlock,互斥锁不会被释放,那么再去上锁,必然死锁。
程序输出:
刚开始我看到这结果,程序执行完毕了,我也懵了,怎么没有阻塞,难道程序有问题,墨迹了好长时间,突然想到看到只出现了一个end main,顿悟子进程没有释放。然后去
ps aux | grep main,发现./main没有执行完毕,这就是子进程死锁了。
所以,在编写多线程程序时,尽量不要用fork(),如果非要使用,可以使用pthread_atfork()来在fork()时打扫战场。
使用pthread_atfork()打扫战场:
#include <stdio.h>
#include <time.h>
#include <pthread.h>
#include <unistd.h>
#include <iostream>
using namespace std;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* doit(void *arg)
{
cout<<"pid = "<<static_cast<int>(getpid())<<" begin doit"<<endl;
pthread_mutex_lock(&mutex);
struct timespec ts = {3, 0}; //second and nanosecond
nanosleep(&ts, NULL);
pthread_mutex_unlock(&mutex);
cout<<"pid = "<<static_cast<int>(getpid())<<" end doit"<<endl;
return NULL;
}
void prepare()
{
pthread_mutex_unlock(&mutex);
}
void parent()
{
pthread_mutex_lock(&mutex);
}
int main()
{
pthread_atfork(prepare, parent, NULL);
cout<<"pid = "<<static_cast<int>(getpid())<<" entering main"<<endl;
pthread_t tid;
pthread_create(&tid, NULL, doit, NULL);
struct timespec ts = {1, 0};
nanosleep(&ts, NULL);
if(fork() == 0){
doit(NULL);
}
pthread_join(tid, NULL);
cout<<"pid = "<<static_cast<int>(getpid())<<" end main"<<endl;
return 0;
}
在fork()时,主进程先调用prepare()函数去unlock(),这样子进程中得到的mutex就是解锁状态。同时主进程执行parent()函数再次加上锁,防止主进程中正在执行doit()函数的线程取unlock()一个已经解锁了得锁,这会发生no such file pthread_unlock().c的错误,你懂的,我在这个错误上以前可是吃过大亏的。