关于线程的创建与使用,前几个博客说的很详细了,那么我们如果在某一个函数线程中调用fork函数有什么需要注意的吗?
在线程中调用 fork 函数,子进程只会启用调用 fork 函数的那条线程,其他线程不会启用。
示例代码:
#include
#include
#include
#include
#include
#include
//测试线程中调用frok的结果
void *fun(void *arg)
{
int i=0;
int n=fork();
if(n==0)
{
for(;i<4;i++)
{
sleep(1);
printf("%d pig heng heng heng\n",getpid());
}
}
else
{
for(;i<3;i++)
{
sleep(1);
printf("%d cat miao miao miao\n",getpid());
}
}
}
int main()
{
pthread_t pthid;
int res=pthread_create(&pthid,0,fun,0);
assert(res==0);
int i=0;
for(;i<5;i++)
{
printf("%d dog wang wang wang\n",getpid());
sleep(1);
}
pthread_exit(NULL);
}
执行结果:
可以看出主进程中的输出并没有被再次执行,只打印了5次dog,子进程的确只调用了使用fork的函数线程
子进程会继承其父进程的锁以及锁的状态,但是父子进程用的不是同一把锁,父进程解锁并不会影响到子进程的锁,即fork以后子进程和父进程使用的是两把锁。那么子进程就有可能死锁,比如我们在fork之前,一个线程对某个锁进行的lock操作,即持有了该锁,然后另外一个线程调用了fork创建子进程。可是在子进程中持有那个锁的线程却"消失"了,从子进程的角度来看,这个锁被“永久”的上锁了,因为它的持有者“蒸发”了,所以如果子进程中的这个线程对这个已经被持有的锁进行lock操作的话,就会发生死锁。
可能有人会想到可以在fork之前让调用fork的线程获取所有的锁,然后再在fork出的子进程的中释放每一个锁。这种方法是可以的,但是这种做法会带来一个问题,那就是隐含了一种上锁和解锁的先后顺序,如果次序不同,就会发生死锁。
比如,子进程继承来了父进程中的锁A和锁B,现在需要在自己的线程中对其解锁(当然也是在自己的线程中加锁的,我没有画),如果向我上面那种画法,就会出现死锁。因为先对A加锁然后对B加锁,那么你在对B进行解锁前应该先对A解锁,但是对A解锁在对B解锁之后,所以会发生死锁。所以这种方法很麻烦,但是有人不厌其烦说可以做到控制顺序,但是这种线程中调用fork还是有很多问题是不能控制的(比如库函数等),所以系统为我们解决了这种问题,提供给了我们一个函数
int thread_atfork
(
void
(*prepare)(
void
),
void
(*parent)(
void
),
void
(*child)(
void
));
上述并不是加锁一次解锁了两次,而是各自独自解锁。因为子进程地址空间创建的时候,得到了所有父进程定义的锁的副本,继承的是父进程的锁的拷贝。任何一个进程修改锁的状态就会为另一个进程拷贝一份滴(写时拷贝),所以并没有加锁一次解锁两次,这也是我上面说的父子进程用的不是同一把锁,各自操作各自的锁就没有影响啦。
需要注意的是pthread_atfork只能清理锁,但不能清理条件变量。在有些系统的实现中条件变量不需要清理。但是在有的系统中,条件变量的实现中包含了锁,这种情况就需要清理。但是目前并没有清理条件变量的接口和方法。
测试用例:
#include
#include
#include
#include
#include
#include
//使用pthread_atfork函数进行获取锁
pthread_mutex_t mutex;
void *fun(void *arg)
{
printf("fun will get pthread\n");
pthread_mutex_lock(&mutex);
printf("fun mutex locked\n");
sleep(5);
pthread_mutex_unlock(&mutex);
printf("fun mutex unlcok\n");
}
void prepare() //pthread_atfork一旦被调用就会执行该函数,但是需要等待fun释放锁才可以将锁加上
{
pthread_mutex_lock(&mutex);
printf("prepare get mutex\n");
}
void parent()
{
pthread_mutex_unlock(&mutex);
printf("parent progress get mutex\n");
}
void child()
{
pthread_mutex_unlock(&mutex);
printf("child progress get mutex\n");
}
int main()
{
pthread_mutex_init(&mutex,NULL);
pthread_t id;
int res=pthread_create(&id,NULL,fun,NULL);
assert(res==0);
sleep(1);
pthread_atfork(prepare,parent,child);
if(fork()) //父进程与子进程相当与加了两把不同的锁了,没有什么关系了
{
printf("father will get mutex\n");
pthread_mutex_lock(&mutex);
sleep(1);
printf("father lock success\n");
pthread_mutex_unlock(&mutex);
printf("father over\n");
}
else
{
printf("child will get mutex\n");
pthread_mutex_lock(&mutex);
printf("child lock success\n");
sleep(2);
pthread_mutex_unlock(&mutex);
printf("child over\n");
}
}
指定在fork调用之前,建子进程之前,调用prepare函数,获取所有的锁,然后创建子进程,子进程创建以后,父进程环境中调用parent所有的锁,子进程环境中调用child解所有的锁,然后fork函数再返回。这样保证了fork之后,子进程拿到的锁都是解锁状态,避免死锁。