线程与exec
只要有任一线程调用了exec()系列函数之一时,调用程序将被完全替换。除了调用exec()的线程之外,其他所有线程都将立即消失。没有任何线程会针对线程特有数据执行结构函数,也不会调用清理函数。该进程的所有互斥量(为进程私有)和属于进程的条件变量都会消失。调用exec()之后,调用线程的线程ID是不确定的。
线程和fork
当多线程进程调用fork()时,仅会将发起调用的线程复制到子进程中。子进程中该线程的线程ID与父进程中发起fork()调用线程的线程ID相一致(后面会有例子说明)。其他线程均在子进程中消失,也不会为这些线程调用清理函数以及针对线程特有数据的解构函数。这将导致如下一些问题。
- 虽然只将发起调用的线程复制到子进程中,但全局变量的状态以及所有的Pthreads对象(如互斥量、条件变量等)都会在子进程中得以保留。(因为在父进程中为这些Pthreads对象分配了内存,而子进程则获得了该内存的一份拷贝。)这会导致很棘手的问题。例如,假设在调用fork()时,另一线程已然锁定了某一互斥量,且对某一全局数据结构的更新也做到了一半。此时,子进程中的该线程无法解锁这一互斥量(因为其并非该互斥量的属主),如果试图获取这一互斥量,线程会遭阻塞。此外,子进程中的全局数据结构拷贝可能也处于不一致状态,因为对其进行更新的线程在执行到一半时消失了。
- 因为并未执行清理函数和针对线程特有数据的解构函数,多线程程序的fork()调用会导致子进程的内存泄露。另外,子进程的线程很可能无法访问(父进程中)由其他线程所创建的线程特有数据项,因为(子进程)没有相应的引用指针。
由于这些问题,推荐在多线程程序中调用fork()的唯一情况时:其后紧跟对exec()的调用。因为新程序会覆盖原有内存,exec()将导致子进程的所有Pthreads对象消失。对于那些必须执行fork(),而其后又无exec()跟随的程序来说,Pthreads API提供了一种机制:fork处理函数。可以利用函数pthread_atfork来创建fork处理函数。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>void *handle1(void *arg)
{printf("handle1 tid = %d\n", pthread_self());pid_t childpid = fork();switch (childpid){case -1:{perror("fork()");exit(EXIT_FAILURE);}case 0:{printf("fork tid = %d\n", pthread_self());_exit(EXIT_SUCCESS);}default:{exit(EXIT_SUCCESS);}}
}void *handle2(void *arg)
{printf("handle2 tid = %d\n", pthread_self());return NULL;
}int main(int argc, char *argv[])
{pthread_t tid1, tid2;pthread_create(&tid1, 0, handle1, NULL);pthread_create(&tid2, 0, handle2, NULL);pthread_join(tid1, NULL);pthread_join(tid2, NULL);
}
输出信息:
handle1 tid = 1084647760
handle2 tid = 1109567824
fork tid = 1084647760
可以看到,fork的tid和线程一的相同
线程和exit()
如果任何线程调用了exit(),或者主线程执行了return,那么所有线程都将消失,也不会执行线程特有数据的解构函数以及清理函数。