【Linux编程】线程终止

当进程中的某一个线程调用了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;
}

运行结果:
【Linux编程】线程终止_第1张图片

根据创建线程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;
}

运行结果:
【Linux编程】线程终止_第2张图片

可以看出,线程2、3、4分别对应上述三种情况,而线程1由于是返回,所以不会调用注册的清理函数。

下表是进程原语和线程原语之间的比较:
【Linux编程】线程终止_第3张图片

参考:
《unix环境高级编程》 P287-P297.

你可能感兴趣的:(【Linux编程】线程终止)