分类: C/C++
2013-06-11 22:02:49
原文地址:关于线程清理函数的一些细节 作者:ifndef
下面的测试程序我们都省略错误检查
如同进程可以调用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。