当进程中的某一个线程调用了exit、_Exit、_exit,那么整个进程会终止。同样,一个信号发送到某个线程,而该信号的默认动作是终止,整个进程也会终止。
单个进程的终止有三种方法:
- 从程序正常返回。
- 线程自身调用pthread_exit。
- 被同一进程中的其它线程取消。
先来看看前两种情况。
void pthread_exit(void *rval_ptr); // 退出线程,可通过参数向其它线程传递信息 int pthread_join(pthread_t thread, void **rvalptr); // 阻塞等待指定线程退出,可获得线程退出时的信息
注意,pthread_join所等待的线程可以以上述三种方式退出,**rvalptr都能获得退出信息。下面是两个函数的测试例程:
#include <stdio.h> #include <pthread.h> void *thr_fn1(void *arg) { printf("thread 1 returning\n"); return ((void *)1); } void *thr_fn2(void *arg) { printf("thread 2 returning\n"); pthread_exit((void *)2); } int main(void) { pthread_t tid1, tid2; void *ret; pthread_create(&tid1, NULL, thr_fn1, NULL); pthread_create(&tid2, NULL, thr_fn2, NULL); pthread_join(tid1, &ret); // 等待线程1退出 printf("thread 1 exit code = %d\n", (int)ret); pthread_join(tid2, &ret); // 等待线程2退出 printf("thread 2 exit code = %d\n", (int)ret); return 0; }
运行结果:
根据创建线程pthread_create函数的要求,新建线程函数原型必须返回一个void型指针。所以,以上例程的转换才看起来如此古怪,实际上是在用指针本身承载数据。在默认情况下,线程的终止状态会保存到对该线程调用pthread_join,所以即使两个子线程早早地退出,而主线程还没有走到pthread_join函数,这也是可以的。
下面介绍第三种退出方式:
int pthread_cancel(pthread_t tid); // 请求取消同一进程中的指定线程
调用这个函数时,被取消线程如果下面的方式退出:
pthread_exit(PTHREAD_CANCELED);
注意,这个函数只是控制线程发出的一种请求,被取消线程不一定会被终止。我在被取消线程中加了一个死循环,于是它就无法响应这个退出请求,原因不清楚。
一个线程退出时,可以调用多个之前注册的线程清理处理程序。使用下面两个函数:
void pthread_cleanup_push(void (*rtn)(void *), void *arg); // 注册清理函数 void pthread_cleanup_pop(int execute); // 根据参数决定是否调用清理函数
注册的清理函数的调用顺序和栈一样,是后进先出类型的,就如同他们的名字一样。清理函数在下列情况下会被调用:
- 线程调用pthread_exit时。
- 响应其它线程发来的pthread_cancel取消请求时。
- pthread_cleanup_pop的参数不为0时。
这里有一点需要注意,push和pop两个函数以宏的形式实现的,所以必须成对出现。下面是一个测试例程:
#include <stdio.h> #include <pthread.h> // 线程清理处理程序 void cleanup(void *arg) { printf("cleanup : %s\n", (char *)arg); } void *thr_fn1(void *arg) { pthread_cleanup_push(cleanup, "This is thread 1\n"); return ((void *)1); // 返回不会调用清理函数 pthread_cleanup_pop(0); // 配对作用 } void *thr_fn2(void *arg) { pthread_cleanup_push(cleanup, "This is thread 2\n"); pthread_exit((void *)2); // exit时会调用清理函数 pthread_cleanup_pop(0); // 配对作用 } void *thr_fn3(void *arg) { pthread_cleanup_push(cleanup, "This is thread 3\n"); sleep(3); // 等待控制线程发出取消请求,然后调用清理函数 pthread_cleanup_pop(0); // 配对作用 } void *thr_fn4(void *arg) { pthread_cleanup_push(cleanup, "This is thread 4\n"); pthread_cleanup_pop(1); // 参数为0,则清理函数不被调用 } int main(void) { pthread_t tid1, tid2, tid3, tid4; pthread_create(&tid1, NULL, thr_fn1, NULL); pthread_create(&tid2, NULL, thr_fn2, NULL); pthread_create(&tid3, NULL, thr_fn3, NULL); pthread_create(&tid4, NULL, thr_fn4, NULL); pthread_cancel(tid3); sleep(3); return 0; }
运行结果:
可以看出,线程2、3、4分别对应上述三种情况,而线程1由于是返回,所以不会调用注册的清理函数。
参考:
《unix环境高级编程》 P287-P297.