Linux线程(4)——pthread_detach()自动回收线程资源

文章介绍了线程的分离,通过pthread_detach()函数可以让线程在结束时自动回收资源,而无法再通过pthread_join()获取状态。此外,还讨论了线程清理函数,使用pthread_cleanup_push()和pthread_cleanup_pop()注册和执行线程退出时的清理操作,清理函数的执行顺序与注册顺序相反。
摘要由CSDN通过智能技术生成

分离线程        

        默认情况下,当线程终止时,其它线程可以通过调用 pthread_join()获取其返回状态、回收线程资源,有时,程序员并不关心线程的返回状态,只是希望系统在线程终止时能够自动回收线程资源并将其移除。在这种情况下,可以调用 pthread_detach()将指定线程进行分离,也就是分离线程,pthread_detach()函数原型如下所示:

#include <pthread.h>

int pthread_detach(pthread_t thread);

        使用该函数需要包含头文件,参数 thread 指定需要分离的线程,函数 pthread_detach()调用成功将返回 0;失败将返回一个错误码。

        一个线程既可以将另一个线程分离,同时也可以将自己分离,譬如:

pthread_detach(pthread_self());

        一旦线程处于分离状态,就不能再使用 pthread_join()来获取其终止状态,此过程是不可逆的,一旦处于分离状态之后便不能再恢复到之前的状态。处于分离状态的线程,当其终止后,能够自动回收线程资源。

使用示例

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>

static void *new_thread_start(void *arg){
    int ret;
    /* 自行分离 */
    ret = pthread_detach(pthread_self());
    if (ret) {
       fprintf(stderr, "pthread_detach error: %s\n", strerror(ret));
       return NULL;
    }
    printf("新线程 start\n");
    sleep(2); //休眠 2 秒钟
    printf("新线程 end\n");
    pthread_exit(NULL);
}

int main(void){
    pthread_t tid;
    int ret;

    /* 创建新线程 */
    ret = pthread_create(&tid, NULL, new_thread_start, NULL);
    if (ret) {
        fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
        exit(-1);
    }
    sleep(1); //休眠 1 秒钟

    /* 等待新线程终止 */
    ret = pthread_join(tid, NULL);
    if (ret)
        fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
    pthread_exit(NULL);
}

        示例代码中,主线程创建新的线程之后,休眠 1 秒钟,调用 pthread_join()等待新线程终止;新线程调用 pthread_detach(pthread_self())将自己分离,休眠 2 秒钟之后 pthread_exit()退出线程;主线程休眠 1 秒钟是能够确保调用 pthread_join()函数时新线程已经将自己分离了,所以按照上面的介绍可知,此时主线程调用 pthread_join()必然会失败,测试结果如下:

         打印结果正如我们所料,主线程调用 pthread_join()确实会出错,错误提示为“Invalid argument”。

注册线程清理处理函数

        之前学习进程的时候,我们学习了 atexit()函数,使用 atexit()函数注册进程终止处理函数,当进程调用 exit()退出时就会执行进程终止处理函数;其实,当线程退出时也可以这样做,当线程终止退出时,去执行这样的处理函数, 我们把这个称为线程清理函数(thread cleanup handler)。

        与进程不同,一个线程可以注册多个清理函数,这些清理函数记录在栈中,每个线程都可以拥有一个清理函数栈,栈是一种先进后出的数据结构,也就是说它们的执行顺序与注册(添加)顺序相反,当执行完所有清理函数后,线程终止。

        线程通过函数 pthread_cleanup_push()和 pthread_cleanup_pop()分别负责向调用线程的清理函数栈中添加和移除清理函数,函数原型如下所示:

#include <pthread.h>

void pthread_cleanup_push(void (*routine)(void *), void *arg);
void pthread_cleanup_pop(int execute);

        调用 pthread_cleanup_push()向清理函数栈中添加一个清理函数,第一个参数 routine 是一个函数指针, 指向一个需要添加的清理函数,routine()函数无返回值,只有一个 void *类型参数;第二个参数 arg,当调用清理函数 routine()时,将 arg 作为 routine()函数的参数。

        既然有添加,自然就会伴随着删除,就好比入栈和出栈,调用函数 pthread_cleanup_pop()可以将清理函数栈中最顶层(也就是最后添加的函数,最后入栈)的函数移除。

        当线程执行以下动作时,清理函数栈中的清理函数才会被执行:

  1. 线程调用 pthread_exit()退出时;
  2. 线程响应取消请求时;
  3. 用非 0 参数调用 pthread_cleanup_pop()

        除了以上三种情况之外,其它方式终止线程将不会执行线程清理函数,譬如在线程 start 函数中执行 return 语句退出时不会执行清理函数。

        函数 pthread_cleanup_pop()的 execute 参数,可以取值为 0,也可以为非 0;如果为 0,清理函数不会被调用,只是将清理函数栈中最顶层的函数移除;如果参数 execute 为非 0,则除了将清理函数栈中最顶层的函数移除之外,还会清理该函数。

        尽管我们将 pthread_cleanup_push() 和 pthread_cleanup_pop()称之为函数,但它们是通过宏来实现, 可展开为分别由{和}所包裹的语句序列,所以必须在与线程相同的作用域中以匹配对的形式使用,必须一一对应着来使用,譬如:

pthread_cleanup_push(cleanup, NULL);
pthread_cleanup_push(cleanup, NULL);
pthread_cleanup_push(cleanup, NULL);
......
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);

使用示例

        上文给出了一个使用线程清理函数的例子(虽然例子并没有什么实际作用),但它描述了其中所涉及到的清理机制。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>

static void cleanup(void *arg){
    printf("cleanup: %s\n", (char *)arg);
}

static void *new_thread_start(void *arg){
    printf("新线程--start run\n");
    pthread_cleanup_push(cleanup, "第 1 次调用");
    pthread_cleanup_push(cleanup, "第 2 次调用");
    pthread_cleanup_push(cleanup, "第 3 次调用");
    sleep(2);
    pthread_exit((void *)0); //线程终止

    /* 为了与 pthread_cleanup_push 配对,不添加程序编译会通不过 */
    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);
}

int main(void)
{
    pthread_t tid;
    void *tret;
    int ret;
    
    /* 创建新线程 */
    ret = pthread_create(&tid, NULL, new_thread_start, NULL);
    if (ret) {
        fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
        exit(-1);
    }
    
    /* 等待新线程终止 */
    ret = pthread_join(tid, &tret);
    if (ret) {
        fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
        exit(-1);
    }
    printf("新线程终止, code=%ld\n", (long)tret);
    exit(0);
}

        主线程创建新线程之后,调用 pthread_join()等待新线程终止;新线程调用 pthread_cleanup_ push()函数添加线程清理函数,调用了三次,但每次添加的都是同一个函数,只是传入的参数不同;清理函数添加完成, 休眠一段时间之后,调用 pthread_exit()退出。之后还调用了 3 次 pthread_cleanup_pop(),在这里的目的仅仅只是为了与 pthread_cleanup_push()配对使用,否则编译不通过。接下来编译运行:

         从打印结果可知,先添加到线程清理函数栈中的函数后执行,添加顺序与执行顺序相反。

        将新线程中调用的 pthread_exit()替换为 return,在进行测试,发现并不会执行清理函数。 有时在线程功能设计中,线程清理函数并不一定需要在线程退出时才执行,譬如当完成某一个步骤之 后,就需要执行线程清理函数,此时我们可以调用 pthread_cleanup_pop()并传入非 0 参数,来手动执行线程清理函数,示例代码如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>

static void cleanup(void *arg){
 printf("cleanup: %s\n", (char *)arg);
}

static void *new_thread_start(void *arg){
    printf("新线程--start run\n");
    pthread_cleanup_push(cleanup, "第 1 次调用");
    pthread_cleanup_push(cleanup, "第 2 次调用");
    pthread_cleanup_push(cleanup, "第 3 次调用");
    pthread_cleanup_pop(1); //执行最顶层的清理函数
    printf("~~~~~~~~~~~~~~~~~\n");
    sleep(2);
    pthread_exit((void *)0); //线程终止
    /* 为了与 pthread_cleanup_push 配对 */
    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);
}

int main(void){
    pthread_t tid;
    void *tret;
    int ret;
    /* 创建新线程 */
    ret = pthread_create(&tid, NULL, new_thread_start, NULL);
    if (ret) {
        fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
        exit(-1);
    }
    /* 等待新线程终止 */
    ret = pthread_join(tid, &tret);
    if (ret) {
        fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
        exit(-1);
    }
    printf("新线程终止, code=%ld\n", (long)tret);
    exit(0);
}

        上述代码中,在新线程调用 pthread_exit()之前,先调用 pthread_cleanup_pop(1)手动运行了最顶层的清理函数,并将其从栈中移除,测试结果:

         从打印结果可知,调用 pthread_cleanup_pop(1)执行了最后一次注册的清理函数,调用 pthread_exit()退出线程时执行了 2 次清理函数,因为前面调用 pthread_cleanup_pop()已经将顶层的清理函数移除栈中了,自然在退出时就不会再执行了。

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值