关于线程清理函数的一些细节

作者:ifndef 和c/c++相关  

 

下面的测试程序我们都省略错误检查

如同进程可以调用atexit函数安排在他退出时需要调用的函数一样,进程也可以安排在他退出时调用的
函数。这些清理函数记录在栈中,所以他们执行的顺序和注册的顺序是相反的。

#incldue
void pthread_cleanup_push(void (*rtn)(void *),void *arg);
void pthread_cleanup_pop(int execute);

参数rtn为指向调用的清理函数的指针,arg为传给该清理函数的参数。
 
当线程线程执行下条件时调用清理函数。
  1 调用pthread_exit时
  2响应取消请求时。
  3用非零execute参数调用pthread_cleanup_pop时。
(我们后面还将测试如果线程只是简单的 return 返回,那么会不会调用线程清理函数)

如果execute的参数为0,那么并不调用清理函数。但是无论哪种情况,pthread_cleanup_pop都将
删除最近一次pthread_cleanup_push调用而建立的清理函数。

在具体验证上面三个条件前,还需要注意一点:
  这两个函数可以实现为宏。所以必须在于线程相同的作用域中以匹配的形式调用
  pthread_cleanup_push的宏定义中可包含字符'{',在这种情况下 对应的匹配字符'}'就要在
  pthread_cleanup_pop的定义中存在。

我们先来验证第三个条件,(用非零execute参数调用pthread_cleanup_pop会调用清理函数)
在这个条件中我们会碰到上面提到的细节。

下面这个程序目的很简单。在线程中 注册两个清理函数,然后根据传递过来的参数判断是先调用
pop然后return 还是直接 return 。这样的目的是想看看直接return是不是真的不会调用清理函数,
还排除 调用pop后有调用return 导致的调用清理函数无法分清是谁触发清理函数的调用的。
在主函数中我们 创建了两个线程 分别传递不同的参数 来验证上面所说的。(传递0调用pop后再return,传递非0则直接return返回)
 
  4 void clean(void *arg){
  5         printf("%s\n",(char *)arg);
  6 }
  7
  8 void *thread(void *arg){
  9         printf("thread start\n");
 10         pthread_cleanup_push(clean,"first handler");
 11         pthread_cleanup_push(clean,"second handler");
 12
 13         if((int)arg==0){
 14                 pthread_cleanup_pop(1);
 15                 pthread_cleanup_pop(1);
 16                 return ((void *)0);
 17         }else{
 18                 return ((void *)1);
 19         }
 20 }
 21
 22 int main(void){
 23         pthread_t th;
 24         void *res;
 25
 26         pthread_create(&th,NULL,thread,(void *)1);
 27         pthread_join(th,&res);
 28         printf("first thread exit code:%d\n\n",(int)res);
 29
 30         pthread_create(&th,NULL,thread,(void *)0);
 31         pthread_join(th,&res);
 32         printf("second thread exit code:%d\n",(int)res);
 33
 34         exit(1);
 35 }

但是当我编译这个程序的时候总会出现如下的错误:
test_pthread_clean_return_and_execute.c: In function ‘thread’:
test_pthread_clean_return_and_execute.c:17:3: error: expected ‘while’ before ‘else’

然后就蒙了,不知道什么意思。磨了半天,无奈又回去看书。
然后才发现,我一开始只注意到 push和pop要匹配出现。然后没有注意到他们必须要在相同的
作用域中。应为push和pop的宏定义中包含了'{'和 '}'所以上面的程序。我将pop放在if判断里面
导致,宏扩展后 '{'和'}'匹配错乱。所以导致了编译错误。
所以我们将  线程启动函数 void *thread(voif *arg)改为下面这个函数
  8 void *thread(void *arg){
  9         printf("thread start\n");
 10         pthread_cleanup_push(clean,"first handler");
 11         pthread_cleanup_push(clean,"second handler");
 12
 13         if((int)arg!=0){
 14                 return ((void *)1);
 15         }
 16
 17         pthread_cleanup_pop(1);
 18         pthread_cleanup_pop(1);
 19         return ((void *)0);
 20 }

现在我们就可以编译成功了.

我们来看下输出
thread start
first thread exit code:1

thread start
second handler
first handler
second thread exit code:0


从输出中。我们看到 当调用return 直接返回时的确并未调用线程清理函数。
当调用当以 非零参数 调用pop 时线程的确调用了 线程清理函数。并且调用的顺序是和注册时的顺序是相反的

我们可以把 上面正确的线程函数中的pop调用的参数 改为0,那么线程退出时就应该不会调用注册函数
 17         pthread_cleanup_pop(0);
 18         pthread_cleanup_pop(0);
 
 修改后的程序输出如下:
 thread start
first thread exit code:1

thread start
second thread exit code:0
正如我预料的,线程退出时没有调用 清理函数。


后面的验证就简单多了·。我们来验证第一个条件:
当线程调用pthread_exit退出时,会调用清理函数。

  4 void clean(void *arg){
  5         printf("%s\n",(char *)arg);
  6 }
  7 void *thread(void *arg){
  8         printf("thread start:\n");
  9         pthread_cleanup_push(clean,"first handler");
 10         pthread_cleanup_push(clean,"second handler");
 11
 12         if((int)arg){
 13                 pthread_exit((void *)1);
 14         }
 15         pthread_cleanup_pop(0);
 16         pthread_cleanup_pop(0);
 17         return ((void *)0);
 18 }
 19
 20
 21 int main(void){
 22         pthread_t th;
 23         void *res;
 24         pthread_create(&th,NULL,thread,(void *)1);
 25         pthread_join(th,&res);
 26         printf("thread exit code:%d\n",(int)res);
 27         exit(0);
 28 }
我们创建线程时传递的是非零 参数,这样新线程就会以调用pthread_exit函数结束。
至于后面的pop调用。完全是因为push和pop函数必须匹配出现。

程序输出:
thread start:
second handler
first handler
thread exit code:1
线程正确的调用了清理函数

 

再来验证第二个条件(响应取消请求时会调用清理函数)

在默认情况下 当调用
int pthread_cancel(pthread_t tid);
函数会使得 tid标识的线程的行为如同调用了参数为 PTHREAD_CANCELED的pthread_exit函数。

 4 void clean(void *arg){
  5         printf("%s\n",(char *)arg);
  6 }
  7
  8 void *thread(void *arg){
  9         printf("thread start:\n");
 10         pthread_cleanup_push(clean,"first handler");
 11         pthread_cleanup_push(clean,"second handler");
 12
 13         pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);//设置异步取消。使取消动作不必等到下个取消点
 14         while(1);  //等待 主线程取消
 15
 16         pthread_cleanup_pop(0);
 17         pthread_cleanup_pop(0);
 18         return ((void *)0);
 19 }
 20 int main(void){
 21         pthread_t tid;
 22         void *res;
 23
 24         pthread_create(&tid,NULL,thread,(void *)0);
 25         sleep(1);          //睡眠一秒 好让子进程先运行(我们期望在发送取消新线程时,线程已经在运行)
 26         pthread_cancel(tid);
 27         pthread_join(tid,&res);
 28
 29         if(res==PTHREAD_CANCELED){
 30                 printf("thread exit code:%s\n","PTHREAD_CANCELED");
 31         }
 32         exit(0);
 33 }
输出如下:
thread start:
second handler
first handler
thread exit code:PTHREAD_CANCELED
当新线程接收到取消信号时,因为已经设置了异步取消,所以线程会立刻退出。
输出验证了 当线程响应取消时会调用清理函数,并且返回值为PTHREAD_CANCELED。

相关资料:

关于线程清理函数的一些细节来源网络,如有侵权请告知,即处理!

编程Tags: