线程终止

POSIX线程终止相关函数

//头文件
#include <pthread.h>
//API函数
int
pthread_join(pthread_t thread, void **value_ptr); void pthread_exit(void *retval); int pthread_cancel(pthread_t thread); void pthread_cleanup_push(void (*routine)(void*), void *arg); void pthread_cleanup_pop(int execute);

线程终止方式

  单个线程可以通过3种方式退出,可以在不终止整个进程的情况下,停止线程的控制流。

(1)线程可以直接从启动例程(也就是线程函数)中返回,即执行return语句,返回值是线程的退出码。

(2)线程可以被同一进程中的其他线程取消。即其他线程调用pthread_cancel()函数。

(3)线程函数本身调用pthread_exit()。函数返回线程退出后传出来的retval指针。

【说明】

1. pthread_exit()函数的参数retval是一个无类型指针,这与pthread_create函数中传给启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join函数来访问到这个指针。

2. 调用pthread_join()函数将一直阻塞,直到指定的线程(参数thread)终止,终止方式是上面所描述的3种方式。

(1) 如果线程简单地从启动例程中返回,即执行return语句,pthread_join函数的参数value_ptr就包含返回码。

(2) 如果线程被其他线程取消,pthread_join函数的参数value_ptr指向的内存单元就被设置为PTHREAD_CANCELED。

(3) 如果线程是调用pthread_exit()函数退出的,pthread_join函数的参数value_ptr将能获取到pthread_exit()函数返回的retval指针。

3. 如果对线程的返回值不感兴趣,可以将pthread_exit函数的retval置为NULL,这种情况下,pthread_join()函数仍可以等待指定的线程终止,但并不会获取到线程的终止状态。

实例1:获取已终止的线程的退出码。

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <pthread.h>
 4 
 5 void* thr_fn1(void *arg)
 6 {
 7     printf("thread1 returning***\n");
 8     return (void*)1;
 9 }
10 
11 void* thr_fn2(void *arg)
12 {
13     printf("thread2 exiting###\n");
14     pthread_exit((void*)2);
15 }
16 
17 int main(int argc, char *argv[])
18 {
19     int err;
20     pthread_t tid1, tid2;
21     void *tret;
22     
23     if((err=pthread_create(&tid1, NULL, thr_fn1, NULL) != 0)){
24         printf("Error: can`t create thread1!\n");
25     }
26     if((err=pthread_create(&tid2, NULL, thr_fn2, NULL) != 0)){
27         printf("Error: can`t create thread2!\n");
28     }
29     
30     if((err=pthread_join(tid1, &tret)) != 0){
31         printf("Error: can`t join with thread1!\n");
32     }
33     printf("thread1 exit code: %d\n", (int*)tret);
34     if((err=pthread_join(tid2, &tret)) != 0){
35         printf("Error: can`t join with thread2!\n");
36     }
37     printf("thread2 exit code: %d\n", (int*)tret);
38     
39     return 0;
40 }

View Code

## 运行结果:

thread1 returning***
thread2 exiting###
thread1 exit code: 1
thread2 exit code: 2

【分析】从运行结果可以看出,当一个线程通过调用pthread_exit退出或者简单地从启动例程中返回(return语句),进程中的其他线程可以通过调用pthread_join函数获得该进程的退出状态。

【说明】pthread_create和pthread_exit函数的无类型指针参数可以传递的值不止一个,这个指针可以是包含复杂信息的结构的地址,但是注意的是,这个结构指针指向的内存空间在调用者(线程函数)完成调用以后仍然是有效的。例如,在调用线程的栈区上分配了该结构,那么其他线程在使用这个结构时内存内容可能已经改变了。又如,线程在自己的栈区上分配了一个结构,然后把指向这个结构的指针传给pthread_exit,那么调用pthread_join的线程试图使用该结构时,这个栈区有可能已经被撤销,这块内存也已另作他用。

实例2:用局部(自动)变量(在栈区上分配的变量)作为pthread_exit的参数时出现的问题。

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

typedef struct foo{
    int a,b,c,d;
}FOO;

void printfoo(const char *s, const FOO *fp)
{
    printf("%s", s);
    printf("  structure at %p\n", fp);
    printf("  foo.a = %d\n", fp->a);
    printf("  foo.b = %d\n", fp->b);
    printf("  foo.c = %d\n", fp->c);
    printf("  foo.d = %d\n", fp->d);
}

void* thr_fn1(void *arg)
{
    FOO foo={1,2,3,4};
    
    printf("thread 1: thread_id=%lu\n", pthread_self());
    printfoo("thread 1:\n", &foo);
    printf("****** thread 1 exiting\n");
    pthread_exit((void*)&foo);
}

void* thr_fn2(void *arg)
{
    printf("thread 2: thread_id=%lu\n", pthread_self());
    printf("###### thread 2 exiting\n");
    pthread_exit((void*)2);
}

int main(int argc, char *argv[])
{
    int err;
    pthread_t tid1,tid2;
    FOO *fp;
    
    printf("Parent starting the first thread\n");
    if((err=pthread_create(&tid1, NULL, thr_fn1, NULL) != 0)){
        printf("Error: can`t create thread1!\n");
    }
    if((err=pthread_join(tid1, (void*)&fp)) != 0){
        printf("Error: can`t join with thread1!\n");
    }
    
    sleep(1);
    printf("\nParent starting the second thread\n");
    if((err=pthread_create(&tid2, NULL, thr_fn2, NULL) != 0)){
        printf("Error: can`t create thread2!\n");
    }
    sleep(1);
    printfoo("\nParent thread:\n", fp);
    
    return 0;
}

View Code

## 运行结果:

Parent starting the first thread
thread 1: thread_id=140128023041792
thread 1:
structure at 0x7f7219094f00
foo.a = 1
foo.b = 2
foo.c = 3
foo.d = 4
****** thread 1 exiting

Parent starting the second thread
thread 2: thread_id=140128023041792
###### thread 2 exiting

Parent thread:
structure at 0x7f7219094f00
foo.a = 420042496
foo.b = 32626
foo.c = 1
foo.d = 0

【分析】从运行结果可以看出,当主线程访问局部结构时,结构的内容(在线程tid1的栈上分配的)已经发生改变了。即主线程试图访问已退出的tid1线程传给它的结构时,由于该结构是在线程tid1的栈区上定义的,当线程退出时,栈区的内存空间也随之释放掉了,所以读取到的内容是随机值。为了解决这个问题,可以使用动态内存分配(malloc)或者使用全局结构。

 线程取消机制

  在默认情况下,pthread_cancel()函数会使得thread标识的线程的行为表现为如同调用了参数为PTHREAD_CANCELED的pthread_exit()函数,即pthread_exit(PTHREAD_CANCELED)。但是,线程可以选择忽略取消或者控制如何被取消。【注意】pthread_cancel函数并不等待线程终止,它仅仅是提出请求。

实例3:线程取消的使用。

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <pthread.h>
 4 
 5 int done = 0;
 6 int cnt = 0;
 7 
 8 void* thr_fn(void *arg)
 9 {
10     //printf("new thread start\n"); //线程取消点
11     while(!done){
12         cnt++;
13         if(cnt == 10)
14             pthread_testcancel(); //自己设置一个线程取消点
15     }
16     return ((void*)1);
17 }
18 
19 int main(int argc, char *argv[])
20 {
21     int err;
22     pthread_t tid;
23     void *tret;
24     
25     if(0 != (err=pthread_create(&tid, NULL, thr_fn, NULL))){
26         printf("Error: can`t create thread\n");
27         return -1;
28     }
29     pthread_cancel(tid);
30     if(0 != (err=pthread_join(tid, &tret))){
31         printf("Error: can`t join with thread\n");
32         return -2;
33     }
34     printf("thread exit code: %d\n", (int*)tret);
35     printf("cnt = %d\n", cnt);
36     
37     return 0;
38 }

View Code

 ## 运行结果:

thread exit code: -1
cnt = 10

【分析】在主线程中调用了pthread_cancel(tid),在线程的启动例程中,当cnt==10时,调用了pthread_testcancel()函数,这个函数是表示设置一个函数取消点。当线程运行到取消点的时候,线程就会终止。线程退出时的状态码为-1,说明了线程的退出是非正常退出的,而正常退出是的状态码应该是1。

【说明】线程在收到pthread_cancel的取消请求后,可能会忽略、立即取消线程或者运行至取消点再取消线程。系统默认情况下,收到取消请求后,线程会继续运行,直到遇到下一个取消点处终止线程。

取消点:取消点是线程检查它是否被取消的一个点,posix保证在一些函数中会自带取消点,如sleep,accept,write,read,printf等,当执行上述函数时,自动触发线程取消点,使线程终止。

【扩展】实际上,线程是否取消除了与取消点有关外,还和线程的取消状态有关。取消状态分为:PTHREAD_CANCEL_ENABLE(可取消状态,这是系统默认的线程取消状态);PTHREAD_CANCEL_DISABLE(不可取消状态)。当线程的取消状态是PTHREAD_CANCEL_DISABLE时,即使线程收到取消请求在取消点也不会取消线程,直到可取消状态变更为PTHREAD_CANCEL_ENABLE时,线程才会在下一个取消点取消线程。

//设置线程取消点函数
void pthread_testcancel(void);

//
修改线程的取消状态函数 int pthread_setcancelstate(int state, int *oldstate);

【参数说明】
state:设置新状态值。
oldstate:存放原先的取消状态。
【函数说明】该函数会在函数内部设置一个取消点,调用该函数时,如果收到一个取消请求,且取消状态是可取消的,就会立即将线程取消。如果取消状态为不可取消,且没有取消请求,就不会取消,直到两者条件都满足时才会取消函数。

  在线程的属性中还有一个属性与线程的取消有关,即它的取消类型,之前我们所说的取消属于推迟取消,即在调用pthread_cancel函数后,需要等到线程运行至一个取消点时,线程才会被取消而终止线程。

但是,还有一种取类型为异步取消,即当调用pthread_cancel后,线程就会被立即取消,而不用等到线程运行至取消点时再取消线程,取消类型同取消状态一样可以修改。

//修改线程的取消类型函数
int pthread_setcanceltype(int type, int *oldtype);
【参数说明】
type:设置新的取消类型。
oldtype:存放原先的取消类型。
【函数说明】取消类型有:PTHREAD_CANCEL_DEFERRED、PTHREAD_CANCEL_ASYNCHRONOUS。
PTHREAD_CANCEL_DEFERRED:线程接收到取消请求后,直到运行至"取消点"后才取消线程。

PTHREAD_CANCEL_ASYNCHRONOUS:线程接收到取消请求后,立即取消线程。

<说明>线程的“取消状态”和“取消类型”存在于任意一个新建线程中,包括主线程,默认设置是PTHREAD_CANCEL_ENABLE 和 PTHREAD_CANCEL_DEFERRED。

 线程清理处理程序

  线程可以安排它退出时需要调用的函数,这与进程在退出时可以用atexit函数安排退出是类似的。这样的函数被称为线程处理清理程序(thread cleanup handler)。一个线程可以建立多个清理处理程序。处理程序记录在栈中,也就是说它们的执行顺序与它们注册时相反。

//注册线程清理处理程序
void pthread_cleanup_push(void (*rtn)(void*), void *arg);
【参数】
rtn:线程退出时被调用的清理函数。
arg:传入给rtn的参数。
//解除线程清理处理程序 void pthread_cleanup_pop(int execute);

【说明】当线程执行以下动作时,清理函数rtn是由phtread_cleanup_push函数调度的,调用时只传入一个参数arg。

  • 线程函数调用pthread_exit时;
  • 响应取消线程请求时;
  • 用非零execute参数调用pthread_cleanup_pop时。

<注意> 如果pthread_cleanup_pop的execute参数如果设置为0,清理函数rtn将不被调用,也就是说,线程函数执行pthread_cleanup_pop(0)时,在phtread_cleanup_push中注册的清理函数rtn将不被执行,但是

pthread_cleanup_pop函数仍将删除上次在phtread_cleanup_push函数中注册的清理处理程序(或函数)。

【扩展】这两个函数有一个限制,因为它们可以实现为宏,pthread_cleanup_push()与pthread_cleanup_pop()必须成对的出现在线程函数相同的作用域中。

pthread_cleanup_push的宏定义可以包含字符 { ,这种情况下,在与pthread_cleanup_pop的宏定义中要有对应的匹配字符 } 。示例如下:

#define pthread_cleanup_push(rtn,arg) { \
struct _pthread_handler_rec __cleanup_handler, **__head; \
__cleanup_handler.rtn = rtn; \
__cleanup_handler.arg = arg; \
(void) pthread_getspecific(_pthread_handler_key, &__head); \
__cleanup_handler.next = *__head; \
*__head = &__cleanup_handler;

#define pthread_cleanup_pop(ex) \
*__head = __cleanup_handler.next; \
if (ex) (*__cleanup_handler.rtn)(__cleanup_handler.arg); \
}

  如果pthread_cleanup_pop函数的参数execute设置为0,清理将不被调用。但不管发生上述哪种情况,pthread_cleanup_pop都将删除上次pthread_cleanup_push调用建立的清理处理程序。示例如下:

pthread_cleanup_push(routine, &arg);
......
pthread_cleanup_pop(0);
pthread_exit((void*)1);

<说明>当线程函数执行到pthread_exit函数时,pthread_cleanup_pop函数将解除pthread_cleanup_push函数注册的清理处理函数routine,但是不会执行routine中的函数体代码。

实例4:使用线程清理处理程序。

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <pthread.h>
 4 
 5 void cleanup(void *arg)
 6 {
 7     printf("cleanup: %s\n", (char*)arg);
 8 }
 9 
10 void* thr_fn1(void *arg)
11 {
12     printf("thread 1 start\n");
13     pthread_cleanup_push(cleanup, "thread 1 first handler");
14     pthread_cleanup_push(cleanup, "thread 1 secend handler");
15     printf("thread 1 push complete\n");
16     if(arg)
17         return ((void*)11);
18     pthread_cleanup_pop(0);
19     pthread_cleanup_pop(0);
20     return ((void*)12);
21 }
22 
23 void* thr_fn2(void *arg)
24 {
25     printf("thread 2 start\n");
26     pthread_cleanup_push(cleanup, "thread 2 first handler");
27     pthread_cleanup_push(cleanup, "thread 2 secend handler");
28     printf("thread 2 push complete\n");
29     if(arg)
30         pthread_exit((void*)21);
31     pthread_cleanup_pop(0);
32     pthread_cleanup_pop(0);
33     pthread_exit((void*)22);
34 }
35 
36 void* thr_fn3(void *arg)
37 {
38     printf("thread 3 start\n");
39     pthread_cleanup_push(cleanup, "thread 3 first handler");
40     pthread_cleanup_push(cleanup, "thread 3 secend handler");
41     printf("thread 3 push complete\n");
42     if(arg)
43         pthread_exit((void*)31);
44     pthread_cleanup_pop(0);
45     pthread_cleanup_pop(0);
46     pthread_exit((void*)32);
47 }
48 
49 int main(int argc, char *argv[])
50 {
51     int err;
52     pthread_t tid1, tid2, tid3;
53     void *tret;
54     
55     if(0 != (err = pthread_create(&tid1, NULL, thr_fn1, (void*)1) )){
56         printf("Error: can`t create thread 1\n");
57     }
58     if(0 != (err = pthread_create(&tid2, NULL, thr_fn2, (void*)1) )){
59         printf("Error: can`t create thread 2\n");
60     }
61     if(0 != (err = pthread_create(&tid3, NULL, thr_fn3, NULL) )){
62         printf("Error: can`t create thread 3\n");
63     }
64     
65     if(0 != (err = pthread_join(tid1, &tret))){
66         printf("Error: can`t join with thread 1\n");
67     }
68     printf("thread 1 exit code: %d\n", (int*)tret);
69     if(0 != (err = pthread_join(tid2, &tret))){
70         printf("Error: can`t join with thread 2\n");
71     }
72     printf("thread 2 exit code: %d\n", (int*)tret);
73     if(0 != (err = pthread_join(tid3, &tret))){
74         printf("Error: can`t join with thread 3\n");
75     }
76     printf("thread 3 exit code: %d\n", (int*)tret);
77     
78     return 0;
79 }

View Code

## 运行结果:

thread 1 start
thread 1 push complete
thread 1 exit code: 11
thread 2 start
thread 2 push complete
cleanup: thread 2 secend handler
cleanup: thread 2 first handler
thread 3 start
thread 3 push complete
thread 2 exit code: 21
thread 3 exit code: 32

## 分析:

1、线程1是直接执行return语句终止线程的,即return ((void*)11); 没有执行到pthread_cleanup_pop(0); 线程就终止了,并没有执行在pthread_cleanup_push函数中注册的清理函数cleanup,因为它不满足注册的清理函数被调用的那3个条件中的任何一个,所以线程1的退出码为11,即thread 1 exit code: 11。

2、线程2是执行到pthread_exit((void*)21); 时线程就终止了,满足已注册的清理函数被调用的条件。这时将调用在pthread_cleanup_push中注册的清理函数cleanup。从运行结果中可以看到,调用顺序和注册顺序是相反的,这是因为清理函数是记录在栈中的,而栈是一种先进后出的数据结构。特别值得注意的是,

pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
pthread_exit((void*)22);

在线程2的启动例程函数体中,上面的3条语句是没有执行到的,从线程2的退出码结果为:thread 2 exit code: 21 可以证明这一点。

3、线程3是执行到pthread_exit((void*)32); 时线程终止,并且在线程函数体中是执行了两个pthread_cleanup_pop(0); 语句的,所以pthread_cleanup_pop函数会删除掉在前面的pthread_cleanup_push中注册的清理函数cleanup,但是不会执行清理处理函数,线程3的退出码为:thread 3 exit code: 32。

综上所述,可以得出以下结论:

1、如果线程是通过从它的启动例程中调用return语句而终止的话,它的清理处理程序就不会被调用。

2、清理处理程序是按照与它们注册时相反的顺序被调用的。

进程和线程原语的比较

  在默认状态下,线程的终止状态会保存直到对该线程调用pthread_join。但是如果线程已经被分离,线程的底层存储资源可以在线程终止时立即被收回。在线程被分离后,我们不能用pthread_join函数等待它的终止状态,因为对分离线程调用pthread_join会产生未定义的行为。分离线程可以调用pthread_detach()函数。

//线程分离函数
int pthread_detach(pthread_t thread);
【参数】thread:待分离的线程ID值。
【返回值】成功,返回0;失败,返回错误码。

 

posted on 2019-07-24 20:01 Kugle 阅读() 评论() 编辑 收藏

版权声明:本文为yunfan1024原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/yunfan1024/p/11237866.html