• Linux 下子线程的 pthread_cleanup_push() 和 pthread_cleanup_pop() 研究


    线程退出前可能有一些清理工作,但是这部分代码又不会放到线程主体部分,就需要挂接一个或者几个线程“清洁工”来做这部分事情。需要这对兄弟:

    #include<pthread.h>
    
    void pthread_cleanup_push(void (*rtn)(void *), void *arg);
    void pthread_cleanup_pop(int execute);

    显然pthread_cleanup_push() 是挂接 清理函数的,它的返回值类型为 void,有两个入口参数,第一个参数是清理函数函数指针,第二个参数是传给清理函数的 typeless pointer 。

    另一个兄弟 pthread_cleanup_pop() 是来触发清理函数的,是按照相反的顺序来触发清理函数的。而如果它的入口参数 execute 为0值,则对应的清理函数并没有真正的执行。

    例如下面这个例子:

      1 /****************************************************************
      2 #     File Name: thread_cleanup3.c
      3 #     Author   : lintex9527
      4 #     E-Mail   : lintex9527@yeah.net
      5 #  Created Time: Sat 22 Aug 2015 03:25:09 PM HKT
      6 #  Purpose     : 测试清理函数的触发顺序,以及执行与否。
      7 #  Outline     : 
      8 #  Usage       : 
      9 #               --------------------------------------------------
     10 #  Result      : 
     11 #               --------------------------------------------------
     12 *****************************************************************/
     13 #include <stdio.h>
     14 #include <stdlib.h>
     15 #include <pthread.h>
     16 
     17 /* 线程传递给 清理函数 的参数结构体 */
     18 struct argtype{
     19     int a,b;
     20     int result;
     21 };
     22 
     23 void print_argtype(const char *str, struct argtype *p)
     24 {
     25     printf("%s\n", str);
     26     printf("    a = %d, b = %d\n", p->a, p->b);
     27     printf("    result = %d\n", p->result);
     28 }
     29 
     30 /* for thread 1 */
     31 struct argtype entity1 = {
     32     .a = 50,
     33     .b = 5,
     34     .result = 11
     35 };
     36 
     37 /* 以下是3个清理函数 */
     38 void cleanup_add(void *arg)
     39 {
     40     struct argtype *p = (struct argtype *)arg;
     41     p->result = p->a + p->b;
     42     print_argtype("cleanup [add]", p);
     43     //pthread_exit((void *)p->result);
     44 }
     45 
     46 void cleanup_minus(void *arg)
     47 {
     48     struct argtype *p = (struct argtype *)arg;
     49     p->result = p->a - p->b;
     50     print_argtype("cleanup [minus]", p);
     51     //pthread_exit((void *)p->result);
     52 }
     53 
     54 
     55 void cleanup_times(void *arg)
     56 {
     57     struct argtype *p = (struct argtype *)arg;
     58     p->result = p->a * p->b;
     59     print_argtype("cleanup [times]", p);
     60     //pthread_exit((void *)p->result);
     61 }
     62 
     63 /* 子线程1函数,临时地改变了entity1结构体中成员值,检查清理函数执行点 */
     64 void* thr1_fun(void *arg)
     65 { 
     66     printf("Now thread1 [%lu] start:\n", pthread_self());
     67 
     68     pthread_cleanup_push(cleanup_times, (void *)&entity1);  // cleanup_times
     69     entity1.a = 20;
     70     entity1.b = 2;
     71     pthread_cleanup_push(cleanup_minus, (void *)&entity1);  // cleanup_minus
     72     pthread_cleanup_push(cleanup_add, (void *)&entity1);   // cleanup_add
     73     pthread_cleanup_pop(3);  // cleanup_add
     74 
     75     entity1.a = 30;
     76     entity1.b = 3;
     77     pthread_cleanup_pop(1);  // cleanup_minus
     78 
     79     entity1.a = 40;
     80     entity1.b = 4;
     81     pthread_cleanup_pop(1);  // cleanup_times
     82 
     83     entity1.a = 80;
     84     entity1.b = 8;
     85     pthread_exit((void *)entity1.result);
     86 }
     87 
     88 
     89 int main(void)
     90 {
     91     int err;
     92     pthread_t tid1;
     93     void *tret;
     94 
     95     err = pthread_create(&tid1, NULL, thr1_fun, NULL);
     96     err = pthread_join(tid1, &tret);
     97     if (err != 0)
     98     {
     99         perror("pthread_join");
    100         return -1;
    101     }
    102 
    103     printf("In main get result [%d] from thread %lu\n", tret, tid1);
    104     print_argtype("main:", &entity1);
    105 
    106     return 0;
    107 }

    执行结果:

    $ ./thread_cleanup3.exe 
    Now thread1 [140090204903168] start:
    cleanup [add]
        a = 20, b = 2
        result = 22
    cleanup [minus]
        a = 30, b = 3
        result = 27
    cleanup [times]
        a = 40, b = 4
        result = 160
    In main get result [160] from thread 140090204903168
    main:
        a = 80, b = 8
        result = 160

    顺序测试

    在这个例子中,我把 pthread_cleanup_pop(int execute) 中的 execute 都设定为非零值,测试3个清理函数的调用顺序,

    注册的顺序是: cleanup_times --> cleanup_minus --> cleanup_add

    调用的顺序是: cleanup_add   --> cleanup_minus --> cleanup_times

    的的确确是按照相反的顺序调用的。

    执行点测试

    为了测试每一个清理函数的执行点,我在每一个pthread_cleanup_pop() 之前都修改了 结构体 entity1 的域 a,b。经过比对发现每一个 pthread_cleanup_push() 和 pthread_cleanup_pop() 形成一个 pairs,因为它们是基于宏实现的,pthread_cleanup_push() 中包含了一个“{”,而 pthread_cleanup_pop() 中包含了一个“}” 和前面的对应,因此它们必须成对的出现,否则代码通不过编译。经过检查和对比,发现每一个 pairs 虽然在代码形式上互相嵌套,但是它们的执行没有互相嵌套。即在执行最外面的 cleanup_times() 并没有递归调用 cleanup_minus() 继而递归调用 cleanup_times()。

    因此在处理最外面的 cleanup_times() 时屏蔽了从 pthread_cleanup_push(cleanup_minus, xxx) 到 pthread_cleanupo_pop(yyy) (与 cleanup_minus 对应的) 部分的代码。

    而在处理 cleanup_minus() 时屏蔽了从 pthread_cleanup_push(cleanup_add, xxx) 到 pthread_cleanup_pop(yyy) (与 cleanup_add 对应的) 部分的代码。

    因为 pop 顺序和 push 顺序是相反的,那么从第一个 pop 的顺序开始执行: cleanup_add --> cleanup_minus --> cleanup_times.

    但是每一次执行 cleanup_xxx 的参数为什么会不一样的呢?是从哪里开始变化的呢?

    是从线程函数入口上到下,一直到 pthread_cleanup_pop() 部分的参数对当前的 cleanup_xxx() 函数有效。在当前 pthread_cleanup_pop() 下面的语句是对后面一个 pop() 函数起作用的。

    如下面这张图:

    左边的指示线条表征的是每一个 push 入栈的清理函数可访问的资源区;

    右边的双箭头线表征的是 push / pop 对子,虽然在代码形式上有嵌套,但是在函数执行上并不会嵌套执行。

    根据分析,

    entity1.a , entity1.b 传递给 cleanup_add() 函数的值是 20 , 2;

    entity1.a , entity1.b 传递给 cleanup_minus() 函数的值是 30, 3;

    entity1.a , entity1.b 传递给 cleanup_times() 函数的值是 40, 4;

    而最终在 main thread 中可以访问到的 entity1.a, entity1.b 的值是 80 , 8 。那个时候已经没有 清理函数 cleanup_xxx() 去访问 entity1 结构体了。

    另外,我原本在清理函数内部添加了 pthread_exit() 函数,这会出现什么情况呢?比如取消 cleanup_times() 函数里 pthread_exit() 之前的注释,编译运行结果如下:

    $ ./thread_cleanup3.exe 
    Now thread1 [140415830189824] start:
    now cleanup_add.
    cleanup [add]
        a = 20, b = 2
        result = 22
    now cleanup_minus.
    cleanup [minus]
        a = 30, b = 3
        result = 27
    now cleanup_times.
    cleanup [times]
        a = 40, b = 4
        result = 160
    In main get result [160] from thread 140415830189824
    main:
        a = 40, b = 4
        result = 160

    对比之前,发现在 main thread 中的 a,b 值是40, 4 ,这和子线程退出点有关,子线程没有走到下面这一步:

        entity1.a = 40;
        entity1.b = 4;
        printf("now cleanup_times.\n");
        pthread_cleanup_pop(1); // cleanup_times
    
    -------------------------------------------------------------------// 下面没有执行
    entity1.a = 80; entity1.b = 8; printf("thread 1 is exit...\n"); pthread_exit((void *)entity1.result);

    说明提前使用 pthread_exit() 那么各个函数访问的资源就更受限。

    但是在2个及以上的清理函数中添加 pthread_exit() ,会导致线程不断地调用 清理函数,进入死机状态。

    总结就是不要在清理函数中添加 pthread_exit() 。

  • 相关阅读:
    蓝绿发布、灰度发布和滚动发布
    linux redis 设置密码:
    redis配置文件讲解
    17 个方面,综合对比 Kafka、RabbitMQ、RocketMQ、ActiveMQ 四个分布式消息队列
    CentOS 7下安装Redis
    压力测试工具介绍
    【k8s部署kong一】kong k8s 安装 以及可视化管理界面
    Jmeter连接ORACLE数据库
    Jenkins安装插件提示实例似乎已离线问题解决办法
    Fiddler增加显示请求响应时间列
  • 原文地址:https://www.cnblogs.com/LinTeX9527/p/4750546.html
Copyright © 2020-2023  润新知