1. 引言
首先明确一个问题:如果一个多线程程序的某个线程调用了fork函数,那么新创建的子进程里是不会自动创建和父进程相同数量的线程的,它只是调用fork的那个线程的完整复制。并且,子进程会自动继承父进程中(包括父进程在调用fork之前创建的线程)互斥锁的状态。也就是说,父进程中已经被加锁的互斥锁在子进程中也是被锁住的。这就引起了一个问题:子进程可能不清楚从父进程继承过来的互斥锁的具体状态(是加锁状态还是解锁状态)。这个互斥锁可能被加锁了,但并不是由调用fork函数的那个线程锁住的,而是由其他线程锁住的。如果是这种情况,则子进程若再次对该互斥锁执行加锁操作就会导致死锁。下面讨论子进程继承父进程互斥锁的几种情况。
2. 继承父进程创建的锁
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<wait.h>
pthread_mutex_t mutex;
int main()
{
pthread_mutex_init(&mutex,NULL);
pthread_mutex_lock(&mutex);
int pid = fork();
if(pid<0)
{
pthread_join(id,NULL);
pthread_mutex_destroy(&mutex);
return 1;
}
else if(pid == 0)
{
printf("I am in the child,want to get the lock\n");
pthread_mutex_lock(&mutex);
printf("I am running here,oop...\n");
pthread_mutex_unlock(&mutex);
printf("I unlocked\n");
exit(0);
}
else
{
sleep(3);
pthread_mutex_unlock(&mutex);
printf("unlocked\n");
wait(NULL);
}
pthread_join(id,NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
死锁!可以看到,父进程之前先加了锁,子进程继承了加锁的状态后,尝试加锁时被阻塞,即便3秒后父进程解了锁,子进程还是不能进行加锁操作,因为fork之后子进程对父进程的动作是不可见的,因此子进程陷入了永远的阻塞状态。
3. 父进程的子线程在fork前执行了加锁操作
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
#include<wait.h>
pthread_mutex_t mutex;
void* another(void* arg)
{
printf("in child thread\n");
pthread_mutex_lock(&mutex);
printf("lock\n");
sleep(5);
pthread_mutex_unlock(&mutex);
printf("unlocked the mutex\n");
}
int main()
{
pthread_mutex_init(&mutex,NULL);
pthread_t id;
pthread_create(&id,NULL,another,NULL);
sleep(1);
int pid = fork();
if(pid<0)
{
pthread_join(id,NULL);
pthread_mutex_destroy(&mutex);
return 1;
}
else if(pid == 0)
{
printf("I am in the child,want to get the lock\n");
pthread_mutex_lock(&mutex);
printf("I am running here,oop...\n");
pthread_mutex_unlock(&mutex);
printf("I unlocked\n");
exit(0);
}
else
{
wait(NULL);
}
pthread_join(id,NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
死锁!fork之前睡眠1秒是为了让子线程先执行加锁。可以看到,子线程加锁后,子进程继承了互斥锁的状态,无法执行加锁操作,被阻塞。即便后来子线程解了锁,子进程还是继续阻塞。说明子进程只是继承了锁的状态,对后来的解锁动作并不可见。
4. 子线程在fork之后加锁
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
#include<wait.h>
pthread_mutex_t mutex;
void* another(void* arg)
{
printf("in child thread\n");
sleep(2);
pthread_mutex_lock(&mutex);
printf("lock\n");
pthread_mutex_unlock(&mutex);
printf("unlocked the mutex\n");
}
int main()
{
pthread_mutex_init(&mutex,NULL);
pthread_t id;
pthread_create(&id,NULL,another,NULL);
int pid = fork();
if(pid<0)
{
pthread_join(id,NULL);
pthread_mutex_destroy(&mutex);
return 1;
}
else if(pid == 0)
{
printf("I am in the child,want to get the lock\n");
pthread_mutex_lock(&mutex);
printf("I am running here,oop...\n");
sleep(5);
pthread_mutex_unlock(&mutex);
printf("I unlocked\n");
exit(0);
}
else
{
wait(NULL);
}
pthread_join(id,NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
正常!在fork之前子线程还没有加锁,子进程成功加锁,再解锁前,子线程也执行了加锁操作,之后两者都顺利解了锁。说明子进程继承的只是fork之前父进程(包括其子线程)已执行过的锁操作状态,fork之后父子各自对锁的操作是不可见的。
5. 子线程创建的线程中执行了加锁
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
#include<wait.h>
pthread_mutex_t mutex;
void* another(void* arg)
{
printf("in child thread\n");
// sleep(2);
pthread_mutex_lock(&mutex);
printf("lock\n");
sleep(5);
pthread_mutex_unlock(&mutex);
printf("unlocked the mutex\n");
}
void* childpthread(void* arg)
{
pthread_t thid;
pthread_create(&thid,NULL,another,NULL);
pthread_join(thid,NULL);
}
int main()
{
pthread_mutex_init(&mutex,NULL);
pthread_t id;
pthread_create(&id,NULL,childpthread,NULL);
sleep(3);
int pid = fork();
if(pid<0)
{
pthread_join(id,NULL);
pthread_mutex_destroy(&mutex);
return 1;
}
else if(pid == 0)
{
printf("I am in the child,want to get the lock\n");
pthread_mutex_lock(&mutex);
printf("I am running here,oop...\n");
sleep(5);
pthread_mutex_unlock(&mutex);
printf("I unlocked\n");
exit(0);
}
else
{
wait(NULL);
}
pthread_join(id,NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
死锁!锁的状态仍然被子进程继承了。