当前位置:嗨网首页>书籍在线阅读

08-线程和进程控制

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

33.3 线程和进程控制

与信号机制类似,exec()、fork()和exit()的问世均早于Pthreads API。接下来的段落将指出在多线程程序中使用此类系统调用所应关注的细节。

线程和exec()

只要有任一线程调用了exec()系列函数之一时,调用程序将被完全替换。除了调用exec()的线程之外,其他所有线程都将立即消失。没有任何线程会针对线程特有数据执行解构函数(destructor),也不会调用清理函数(cleanup handler)。该进程的所有互斥量(为进程私有)和属于进程的条件变量都会消失。调用exec()之后,调用线程的线程ID是不确定的。

线程和fork()

当多线程进程调用fork()时,仅会将发起调用的线程复制到子进程中。(子进程中该线程的线程ID与父进程中发起fork()调用线程的线程ID相一致。)其他线程均在子进程中消失,也不会为这些线程调用清理函数以及针对线程特有数据的解构函数。这将导致如下一些问题。

  • 虽然只将发起调用的线程复制到子进程中,但全局变量的状态以及所有的Pthreads对象(如互斥量、条件变量等)都会在子进程中得以保留。(因为在父进程中为这些Pthreads对象分配了内存,而子进程则获得了该内存的一份拷贝。)这会导致很棘手的问题。例如,假设在调用fork()时,另一线程已然锁定了某一互斥量,且对某一全局数据结构的更新也做到了一半。此时,子进程中的该线程无法解锁这一互斥量(因为其并非该互斥量的属主),如果试图获取这一互斥量,线程会遭阻塞。此外,子进程中的全局数据结构拷贝可能也处于不一致状态,因为对其进行更新的线程在执行到一半时消失了。
  • 因为并未执行清理函数和针对线程特有数据的解构函数,多线程程序的fork()调用会导致子进程的内存泄漏。另外,子进程中的线程很可能无法访问(父进程中)由其他线程所创建的线程特有数据项,因为(子进程)没有相应的引用指针。

由于这些问题,推荐在多线程程序中调用fork()的唯一情况是:其后紧跟对exec()的调用。因为新程序会覆盖原有内存,exec()将导致子进程的所有Pthreads对象消失。

对于那些必须执行fork(),而其后又无exec()跟随的程序来说,Pthreads API提供了一种机制:fork处理函数(handler)。可以利用函数pthread_atfork()来创建fork处理函数,格式如下:

860.png 每一次pthread_atfork()调用都会将prepare_func添加到一个函数列表中,在调用fork()创建新的子进程之前,会(按与注册次序相反的顺序)自动执行该函数列表中的函数。与之类似,会将parent_func和child_func添加到一函数列表中,在fork()返回前,将分别在父、子进程中(按注册顺序)自动运行。

在使用线程的函数库中,有时候fork处理函数很实用。如果没有这一机制,对于那些随意调用了此函数库和fork(),又对函数库创建的其他线程一无所知的应用程序,函数库还真是无计可施。

调用fork()所产生的子进程从调用fork()的线程处继承fork处理函数。执行exec()期间,fork处理函数将不再保留(因为处理函数的代码会在执行exec()的过程中遭到覆盖)。

关于fork处理函数及其使用的更多细节可以参考[Butenhof,1996]。

在Linux上,如果使用NPTL线程库的程序执行了vfork(),那么将不再调用fork处理函数。不过,在使用LinuxThreads程序的同一情况下却有效。

线程与exit()

如果任何线程调用了exit(),或者主线程执行了return,那么所有线程都将消失,也不会执行线程特有数据的解构函数以及清理函数。