欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  科技

线程终止

程序员文章站 2022-04-12 11:37:45
POSIX线程终止相关函数 线程终止方式 单个线程可以通过3种方式退出,可以在不终止整个进程的情况下,停止线程的控制流。 (1)线程可以直接从启动例程(也就是线程函数)中返回,即执行return语句,返回值是线程的退出码。 (2)线程可以被同一进程中的其他线程取消。即其他线程调用pthread_ca ......

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 }

## 运行结果:

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;
}

## 运行结果:

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 }

 ## 运行结果:

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 }

## 运行结果:

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;失败,返回错误码。