fork()需要注意的一些问题

        fork函数用户创建一个和自身一样的子进程,子进程是父进程的副本,子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份拷贝,包括文本、数据和bss段、堆以及用户栈等。子进程还获得与父进程任何打开文件描述符相同的拷贝(比如子进程可以读写父进程中任何打开的文件),他们有完全独立的拷贝(当然linux使用了cow技术,只有改变才会真的拷贝),就是说两边的修改互不影响,父进程和子进程之间最大的区别在于它们有着不同的PID。一次调用两次返回,> 0表示是父进程, == 0表示是子进程。fork产生的问题主要是:

1) 拷贝了父进程的资源,缓冲区等,所以子进程是有状态的,比如我们调用printf的时候会把数据写入缓冲区,子进程也会继承过来,那么在子进程进行打印的时候就可能会打印出本来在父进程写入缓冲区的内容。


2)fork的时候只复制当前线程到子进程,其他线程就没有了,这就会导致很多问题,比较典型的问题就是锁不被释放。

       比如我们在父进程中有一段代码 加锁之后进行了fork,而这个解锁工作是在另外一个线程进行的,那么子进程会继承这个锁(处于锁住状态),导致锁不能被释放。

       那么一般解决这个问题的方法有几种: 

a. 确保父进程是单线程的;

b. fork之后立马调用exec函数,这样会使用完全新的地址空间替换原有的空间。

c. fork_atfork
函数原型为 int pthread_atfork(void (*prepare)(void), void (*parent)void(), void (*child)(void));
prepare处理函数由父进程在fork创建子进程前调用,这个函数的任务是获取父进程定义的所有锁。
parent处理函数是在fork创建了子进程以后,但在fork返回之前在父进程环境中调用的。它的任务是对prepare获取的所有锁解锁。
child处理函数在fork返回之前在子进程环境中调用,与parent处理函数一样,它也必须解锁所有prepare中所获取的锁。
因为子进程继承的是父进程的锁的拷贝,所有上述并不是解锁了两次,而是各自独自解锁。可以多次调用pthread_atfork函数从而设置多套fork处理程序,但是使用多个处理程序的时候。处理程序的调用顺序并不相同。parent和child是以它们注册时的顺序调用的,而prepare的调用顺序与注册顺序相反。这样可以允许多个模块注册它们自己的处理程序并且保持锁的层次(类似于多个RAII对象的构造析构层次)。

需要注意的是pthread_atfork只能清理锁,但不能清理条件变量。在有些系统的实现中条件变量不需要清理。但是在有的系统中,条件变量的实现中包含了锁,这种情况就需要清理。另外一些隐含的函数比如malloc我们也是无法干预的。所以此函数不能完全规避问题。


参考 

谨慎使用多线程中的fork http://www.cnblogs.com/liyuan989/p/4279210.html

多线程程序中fork导致的一些问题  http://www.cnblogs.com/zxtp/p/5147356.html


你可能感兴趣的:(Linux,Programming)