• 线程终止


    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***
    ");
     8     return (void*)1;
     9 }
    10 
    11 void* thr_fn2(void *arg)
    12 {
    13     printf("thread2 exiting###
    ");
    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!
    ");
    25     }
    26     if((err=pthread_create(&tid2, NULL, thr_fn2, NULL) != 0)){
    27         printf("Error: can`t create thread2!
    ");
    28     }
    29     
    30     if((err=pthread_join(tid1, &tret)) != 0){
    31         printf("Error: can`t join with thread1!
    ");
    32     }
    33     printf("thread1 exit code: %d
    ", (int*)tret);
    34     if((err=pthread_join(tid2, &tret)) != 0){
    35         printf("Error: can`t join with thread2!
    ");
    36     }
    37     printf("thread2 exit code: %d
    ", (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
    ", fp);
        printf("  foo.a = %d
    ", fp->a);
        printf("  foo.b = %d
    ", fp->b);
        printf("  foo.c = %d
    ", fp->c);
        printf("  foo.d = %d
    ", fp->d);
    }
    
    void* thr_fn1(void *arg)
    {
        FOO foo={1,2,3,4};
        
        printf("thread 1: thread_id=%lu
    ", pthread_self());
        printfoo("thread 1:
    ", &foo);
        printf("****** thread 1 exiting
    ");
        pthread_exit((void*)&foo);
    }
    
    void* thr_fn2(void *arg)
    {
        printf("thread 2: thread_id=%lu
    ", pthread_self());
        printf("###### thread 2 exiting
    ");
        pthread_exit((void*)2);
    }
    
    int main(int argc, char *argv[])
    {
        int err;
        pthread_t tid1,tid2;
        FOO *fp;
        
        printf("Parent starting the first thread
    ");
        if((err=pthread_create(&tid1, NULL, thr_fn1, NULL) != 0)){
            printf("Error: can`t create thread1!
    ");
        }
        if((err=pthread_join(tid1, (void*)&fp)) != 0){
            printf("Error: can`t join with thread1!
    ");
        }
        
        sleep(1);
        printf("
    Parent starting the second thread
    ");
        if((err=pthread_create(&tid2, NULL, thr_fn2, NULL) != 0)){
            printf("Error: can`t create thread2!
    ");
        }
        sleep(1);
        printfoo("
    Parent thread:
    ", 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
    "); //线程取消点
    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
    ");
    27         return -1;
    28     }
    29     pthread_cancel(tid);
    30     if(0 != (err=pthread_join(tid, &tret))){
    31         printf("Error: can`t join with thread
    ");
    32         return -2;
    33     }
    34     printf("thread exit code: %d
    ", (int*)tret);
    35     printf("cnt = %d
    ", 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
    ", (char*)arg);
     8 }
     9 
    10 void* thr_fn1(void *arg)
    11 {
    12     printf("thread 1 start
    ");
    13     pthread_cleanup_push(cleanup, "thread 1 first handler");
    14     pthread_cleanup_push(cleanup, "thread 1 secend handler");
    15     printf("thread 1 push complete
    ");
    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
    ");
    26     pthread_cleanup_push(cleanup, "thread 2 first handler");
    27     pthread_cleanup_push(cleanup, "thread 2 secend handler");
    28     printf("thread 2 push complete
    ");
    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
    ");
    39     pthread_cleanup_push(cleanup, "thread 3 first handler");
    40     pthread_cleanup_push(cleanup, "thread 3 secend handler");
    41     printf("thread 3 push complete
    ");
    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
    ");
    57     }
    58     if(0 != (err = pthread_create(&tid2, NULL, thr_fn2, (void*)1) )){
    59         printf("Error: can`t create thread 2
    ");
    60     }
    61     if(0 != (err = pthread_create(&tid3, NULL, thr_fn3, NULL) )){
    62         printf("Error: can`t create thread 3
    ");
    63     }
    64     
    65     if(0 != (err = pthread_join(tid1, &tret))){
    66         printf("Error: can`t join with thread 1
    ");
    67     }
    68     printf("thread 1 exit code: %d
    ", (int*)tret);
    69     if(0 != (err = pthread_join(tid2, &tret))){
    70         printf("Error: can`t join with thread 2
    ");
    71     }
    72     printf("thread 2 exit code: %d
    ", (int*)tret);
    73     if(0 != (err = pthread_join(tid3, &tret))){
    74         printf("Error: can`t join with thread 3
    ");
    75     }
    76     printf("thread 3 exit code: %d
    ", (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;失败,返回错误码。
  • 相关阅读:
    vim 的配置文件
    linux bash 的自动补全
    linux ping 命令
    linux 安装ifconfig
    dos exist 命令
    linux 用户的添加,组的添加,以及查看
    if else 的.bat 文件
    For 的.bat文件
    rmdir 的.bat文件
    dos set 命令
  • 原文地址:https://www.cnblogs.com/yunfan1024/p/11237866.html
Copyright © 2020-2023  润新知