• 【线程退出】linux线程退出的几个函数


    一、线程退出

    1、pthread_kill

    (1)引用
    #include <pthread.h>
    #include<signal.h>
    
    (2)函数原型
    int pthread_kill(pthread_t thread, int sig);
    (3)参数
    • thread:线程ID
    • sig:sig信号
    (4)返回值
    • 0:调用成功。

    •  ESRCH:线程不存在。。

    • EINVAL:信号不合法
    (4)作用

      向指定的线程传递sig信号。

    5)注意
      别被名字吓到,pthread_kill可不是kill,而是向线程发送signal。还记得signal吗,大部分signal的默认动作是终止进程的运行,所以,我们才要用signal()去抓信号并加上处理函数。向指定ID的线程发送sig信号,如果线程代码内不做处理,则按照信号默认的行为影响整个进程,也就是说,如果你给一个线程发送了SIGQUIT,但线程却没有实现signal处理函数,则整个进程退出。如果要获得正确的行为,就需要在线程内实现signal(SIGKILL,sig_handler)了。
    (6)程序实例
    //使用pthread_kill函数检测一个线程是否还活着
    /******************************* pthread_kill.c *******************************/
    #include<stdio.h>
    #include <pthread.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <unistd.h>
    #include <errno.h>
    #include <signal.h>
    
    
    void *func1()/*1秒钟之后退出*/
    {
        sleep(1);
        printf("线程1(ID:0x%x)退出。
    ",(unsigned int)pthread_self());
        pthread_exit((void *)0);
    }
    
    void *func2()/*5秒钟之后退出*/
    {
        sleep(5);
        printf("线程2(ID:0x%x)退出。
    ",(unsigned int)pthread_self());
        pthread_exit((void *)0);
    }
    
    void test_pthread(pthread_t tid) /*pthread_kill的返回值:成功(0) 线程不存在(ESRCH) 信号不合法(EINVAL)*/
    {
        int pthread_kill_err;
        pthread_kill_err = pthread_kill(tid,0);
    
        if(pthread_kill_err == ESRCH)
            printf("ID为0x%x的线程不存在或者已经退出。
    ",(unsigned int)tid);
        else if(pthread_kill_err == EINVAL)
            printf("发送信号非法。
    ");
        else
            printf("ID为0x%x的线程目前仍然存活。
    ",(unsigned int)tid);
    }
    
    int main()
    {
        int ret;
        pthread_t tid1,tid2;
        
        pthread_create(&tid1,NULL,func1,NULL);
        pthread_create(&tid2,NULL,func2,NULL);
        
        sleep(3);/*创建两个进程3秒钟之后,分别测试一下它们是否还活着*/
        
        test_pthread(tid1);/*测试ID为tid1的线程是否存在*/
        test_pthread(tid2);/*测试ID为tid2的线程是否存在*/
    
        exit(0);
    }
    

      

    //kill给线程发信号

    #include <pthread.h>  
    #include <stdio.h>  
    #include <sys/signal.h>  
      
    #define NUMTHREADS 3  
    void sighand(int signo);  
      
    void *threadfunc(void *parm)  
    {  
        pthread_t             tid = pthread_self();  
        int                   rc;  
      
        printf("Thread %u entered/n", tid);  
        rc = sleep(30); /* 若有信号中断则返回剩余秒数 */  
        printf("Thread %u did not get expected results! rc=%d/n", tid, rc);  
        return NULL;  
    }  
      
    void *threadmasked(void *parm)  
    {  
        pthread_t             tid = pthread_self();  
        sigset_t              mask;  
        int                   rc;  
      
        printf("Masked thread %lu entered/n", tid);  
      
        sigfillset(&mask); /* 将所有信号加入mask信号集 */  
      
        /* 向当前的信号掩码中添加mask信号集 */  
        rc = pthread_sigmask(SIG_BLOCK, &mask, NULL);  
        if (rc != 0)  
        {  
            printf("%d, %s/n", rc, strerror(rc));  
            return NULL;  
        }  
      
        rc = sleep(15);  
        if (rc != 0)  
        {  
            printf("Masked thread %lu did not get expected results! rc=%d /n", tid, rc);  
            return NULL;  
        }  
        printf("Masked thread %lu completed masked work/n", tid);  
        return NULL;  
    }  
      
    int main(int argc, char **argv)  
    {  
        int                     rc;  
        int                     i;  
        struct sigaction        actions;  
        pthread_t               threads[NUMTHREADS];  
        pthread_t               maskedthreads[NUMTHREADS];  
      
        printf("Enter Testcase - %s/n", argv[0]);  
        printf("Set up the alarm handler for the process/n");  
      
        memset(&actions, 0, sizeof(actions));  
        sigemptyset(&actions.sa_mask); /* 将参数set信号集初始化并清空 */  
        actions.sa_flags = 0;  
        actions.sa_handler = sighand;  
      
        /* 设置SIGALRM的处理函数 */  
        rc = sigaction(SIGALRM,&actions,NULL);  
      
        printf("Create masked and unmasked threads/n");  
        for(i=0; i<NUMTHREADS; ++i)  
        {  
            rc = pthread_create(&threads[i], NULL, threadfunc, NULL);  
            if (rc != 0)  
            {  
                printf("%d, %s/n", rc, strerror(rc));  
                return -1;  
            }  
      
            rc = pthread_create(&maskedthreads[i], NULL, threadmasked, NULL);  
            if (rc != 0)  
            {  
                printf("%d, %s/n", rc, strerror(rc));  
                return -1;  
            }  
        }  
      
        sleep(3);  
        printf("Send a signal to masked and unmasked threads/n");  
      
         /* 向线程发送SIGALRM信号 */  
        for(i=0; i<NUMTHREADS; ++i)  
        {  
            rc = pthread_kill(threads[i], SIGALRM);  
            rc = pthread_kill(maskedthreads[i], SIGALRM);  
        }  
      
        printf("Wait for masked and unmasked threads to complete/n");  
        for(i=0; i<NUMTHREADS; ++i) {  
            rc = pthread_join(threads[i], NULL);  
            rc = pthread_join(maskedthreads[i], NULL);  
        }  
      
        printf("Main completed/n");  
        return 0;  
    }  
      
    void sighand(int signo)  
    {  
        pthread_t             tid = pthread_self();  
      
        printf("Thread %lu in signal handler/n", tid);  
        return;  
    }  

    2、pthread_exit

    (1)函数原型
    void pthread_exit(void* retval);
    

    (2)参数

      pthread_exit函数唯一的参数value_ptr是函数的返回代码,只要pthread_join中的第二个参数value_ptr不是NULL,这个值将被传递给value_ptr。

    (3)作用

    线程通过调用pthread_exit函数终止执行,就如同进程在结束时调用exit函数一样。这个函数的作用是,终止调用它的线程并返回一个指向某个对象的指针。
    

    (4)用法

    • 在线程中调用pthread_exit退出线程,在主线程中嗲用pthread_join函数阻塞等待结束并释放资源,pthread_exit的参数会传给pthread_join.
    • 设置线程为分离属性,线程中调用pthread_exit退出线程后系统自动释放资源。
      线程可以调用pthread_exit终止自己,有两种情况需要注意:
    • 在主线程中,如果从main函数返回或是调用了exit函数退出主线程,则整个进程将终止,此时进程中有线程也将终止,因此在主线程中不能过早地从main函数返回;
    • 如果主线程调用pthread_exit函数,则仅仅是主线程消亡进程不会结束,进程内的其他线程也不会终止,直到所有线程结束,进程才会结束

    3、pthread_join

      函数pthread_join用来等待一个线程的结束,线程间同步的操作。 

      pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable(合并)的。

    (1)函数原型
    int pthread_join( pthread_t  thread, void * * value_ptr );
    
    (2)参数
    thread:线程ID
    retval:用户定义的指针,用来存储被等待线程的返回值。
    
    (3)返回值
    0代表成功。 失败,返回的则是错误号。
    
      (4)解释
    • 代码中如果没有pthread_join主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。
    • 通过pthread_join()函数来使主线程阻塞等待其他线程退出,这样主线程可以清理其他线程的环境。但是还有一些线程,更喜欢自己来清理退出的状态,他们也不愿意主线程调用pthread_join来等待他们。我们将这一类线程的属性称为detached(分离的)。如果我们在调用 pthread_create()函数的时候将属性设置为NULL,则表明我们希望所创建的线程采用默认的属性,也就是jionable(此时不是detached)。
     
    4、pthread_cancel
    (1)作用
      用于取消一个函数,它通常需要被取消线程的配合。默认情况(延迟取消),它就是给pthread设置取消标志, pthread线程在很多时候会查看自己是否有取消请求。如果有就主动退出, 这些查看是否有取消的地方称为取消点。
    当然,线程也不是被动的被别人结束。它可以通过设置自身的属性来决定如何结束。
    线程的被动结束分为两种:
    • 异步终结:当其他线程调用pthread_cancel的时候,线程就立刻被结束。
    • 同步终结:同步终结则不会立刻终结,它会继续运行,直到到达下一个结束点(cancellation point)。当一个线程被按照默认的创建方式创建,那么它的属性是同步终结。

      若是在整个程序退出时,要终止各个线程,应该在成功发送 CANCEL 指令后,使用 pthread_join 函数,等待指定的线程已经完全退出以后,再继续执行;否则,很容易产生 “段错误”。

    (2)函数原型
    #include<pthread.h>
    int pthread_cancel(pthread_t thread)
    (3)返回值
    成功则返回0,否则为非0值。发送成功并不意味着thread会终止。
    
    (4)用法
    若是在整个程序退出时,要终止各个线程,应该在成功发送 CANCEL 指令后,使用 pthread_join 函数,等待指定的线程已经完全退出以后,再继续执行;否则,很容易产生 “段错误”。
    int pthread_setcancelstate(int state, int *oldstate)
    设置本线程对Cancel信号的反应,state有两种值:
    • PTHREAD_CANCEL_ENABLE(缺省):收到信号后设为CANCLED状态
    • PTHREAD_CANCEL_DISABLE:忽略CANCEL信号继续运行;

    old_state如果不为 NULL则存入原来的Cancel状态以便恢复。

    int pthread_setcanceltype(int type, int *oldtype)
    设置本线程取消动作的执行时机,
      type由两种取值(仅当Cancel状态为Enable时有效):
    • PTHREAD_CANCEL_DEFERRED:表示收到信号后继续运行至下一个取消点再退出
    • PTHREAD_CANCEL_ASYNCHRONOUS:立即执行取消动作(退出)

      oldtype如果不为NULL则存入原来的取消动作类型值。

      此函数应该在线程开始时执行,若线程内部有任何资源申请等操作,应该选择 PTHREAD_CANCEL_DEFERRED 的设定,然后在退出点(pthread_testcancel 用于定义退出点)进行线程退出。
    void pthread_testcancel(void)
    检查本线程是否处于Canceld状态,如果是,则进行取消动作,否则直接返回。 此函数在线程内执行,执行的位置就是线程退出的位置,在执行此函数以前,线程内部的相关资源申请一定要释放掉,他很容易造成内存泄露

    (5)程序实例

    #include   <stdio.h>
    #include   <stdlib.h>
    #include   <pthread.h>
    #include   <unistd.h>
    
    void* func(void *arg)
    {
       pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);           //允许退出线程
       pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,   NULL);   //设置立即取消
       while(1);
      return NULL;
    }
    
    int main(int argc,char *argv[])
    {
      pthread_t thrd;
      pthread_attr_t attr;
      pthread_attr_init(&attr);
      pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
    
      if(pthread_create(&thrd,&attr,func,NULL))
      {
        perror( "pthread_create   error ");
        exit(EXIT_FAILURE);
      }
    
      if(!pthread_cancel(thrd))
      {
        printf("pthread_cancel OK
    ");
      }
      sleep(10);
      return 0;
    }

    5、exit()

    如:exit(EXIT_SUCCESS),是进程退出,如果在线程函数中调用exit,那改线程的进程也就挂了,会导致该线程所在进程的其他线程也挂掉,比较严重。

    6、return

      return是函数返回,不一定是线程函数,只有线程函数return,线程才会退出
    对比说明:
    • pthread_join一般是主线程来调用,用来等待子线程退出,因为是等待,所以是阻塞的,一般主线程会依次join所有它创建的子线程。
    • pthread_exit一般是子线程调用,用来结束当前线程。子线程可以通过pthread_exit传递一个返回值,而主线程通过pthread_join获得该返回值,从而判断该子线程的退出是正常还是异常。

     二、资源清理

      一旦又处于挂起状态的取消请求(即加锁之后,解锁之前),线程在执行到取消点时如果只是草草收场,这会将共享变量以及pthreads对象(例如互斥量)置于一种不一致状态,可能导致进程中其他线程产生错误结果、死锁,甚至造成程序崩溃。为避免这一问题:

    使用清理函数pthread_cleanup_push()pthread_cleanup_pop()来处理,这两个函数必需成对出现,不然会编译错误。

      不论是可预见的线程种植还是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证种植时能顺利的释放掉自己所占用的资源,包括单独申请的对内存,特别是锁资源,就是一个必需考虑的问题。

    最近常出现的情形时资源独占锁的使用:

      线程为了访问临界共享资源而为其加上锁,但在访问过程呗外界取消,或者发生了中断,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可见的,因此的确需要一个机制来简化用于资源释放的编程。

    在POSIX线程API中提供了一个pthread_clean_push()/pthread_cleanup_pop()函数对,用于自动释放资源----从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作都将执行pthread_cleanup_push()所指定的清理函数。

    API定义如下:

    void pthread_cleanup_push(void (*routine) (void  *),  void *arg)
    void pthread_cleanup_pop(int execute)

    pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的栈结构管理,

    void routine(void *arg)函数在调用pthread_cleanup_push()时压入清理函数栈,多次对pthread_cleanup_push()的调用将在清理函数栈中形成一个函数链,在执行该函数链时按照压栈的相反顺序弹出。execute参数表示执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数,为0表示不执行,非0为执行;这个参数并不影响异常终止时清理函数的执行。

    有三种情况线程清理函数会被调用:

    • 线程还未执行 pthread_cleanup_pop 前,被 pthread_cancel 取消
    • 线程还未执行 pthread_cleanup_pop 前,主动执行 pthread_exit 终止
    • 线程执行 pthread_cleanup_pop,且 pthread_cleanup_pop 的参数不为 0.

    注意:如果线程还未执行 pthread_cleanup_pop 前通过 return 返回,是不会执行清理函数的。

    void routine()函数可参照如下定义:

    void *cleanup(void* p)
    {
            free(p);
            printf("清理函数
    ");
    }
    

     线程主动清理过程的严谨写法:

    void thread_fun(void*p)
    {      p=malloc(20);
            pthread_cleanup_push(cleanup,p);
            printf("子线程
    ");
            sleep(1);            //系统调用,用来响应pthread_cancel函数
            printf("是否杀死了线程
    ");  //如果线程在上一句被杀死,这一句不会被打印
            pthread_exit(NULL);      //不管线程是否被杀死,这一句都会检测清理函数,并执行
            pthread_clean_pop(1);
    }
    

     注意:在子线程中如果申请了单独的堆空间,不应用free直接清理;因为假如在线程中直接free,如果,在free之后线程被取消,清理函数被执行,则会出现重复free的情况。  

    情况如下:

    void thread_fun(void*p)
    {
        p=malloc(20);
           pthread_cleanup_push(cleanup,p);
           printf("子线程
    ");
           sleep(1);            //系统调用,用来响应pthread_cancel函数
           printf("是否杀死了线程
    ");  //如果线程在上一句被杀死,这一句不会被打印
           free(p);      //不管线程是否被杀死,这一句都会检测清理函数,并执行<br>
           //加入函数在此处被cancel ,可能会出现重复free
         sleep(1);//在此处系统调用,响应pthread_cancel(),会执行清理函数.            
           return NULL;
          pthread_clean_pop(1);//由于上一句return,所以这一句不执行,即清理函数不会执行
    }

    pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式实现的,这是pthread.h中的宏定义:

    #define pthread_cleanup_push(routine,arg)                                     
    {
             struct _pthread_cleanup_buffer _buffer;                                   
            _pthread_cleanup_push (&_buffer, (routine), (arg));
    #define pthread_cleanup_pop(execute)                                          
            _pthread_cleanup_pop (&_buffer, (execute)); 
    }
    

      可见,pthread_cleanup_push()带有一个"{",而pthread_cleanup_pop()带有一个"}",因此这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能通过编译。

    在下面的例子里,当线程在"do some work"中终止时,将主动调用pthread_mutex_unlock(mut),以完成解锁动作。


    以下是使用方法:
    pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut);
    pthread_mutex_lock(&mut);
    .......
    pthread_mutex_unlock(&mut);
    pthread_cleanup_pop(0);

    必须要注意的是,如果线程处于PTHREAD_CANCEL_ASYNCHRONOUS状态,上述代码段就有可能出错,因为CANCEL事件有可能在pthread_cleanup_push()和pthread_mutex_lock()之间发生,或者在pthread_mutex_unlock()和pthread_cleanup_pop()之间发生,
    从而导致清理函数unlock一个并没有加锁的mutex变量,造成错误。因此,在使用清理函数的时候,都应该暂时设置成PTHREAD_CANCEL_DEFERRED模式。为此,POSIX的Linux实现中还提供了一对不保证可移植的pthread_cleanup_push_defer_np()/pthread_cleanup_pop_defer_np()
    扩展函数,

    功能与以下
    代码段相当:
    { 
            int oldtype;
            pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);
            pthread_cleanup_push(routine, arg);
             ......
            pthread_cleanup_pop(execute);
            pthread_setcanceltype(oldtype, NULL);
     }    
    补充:
      在线程宿主函数中主动调用return,如果return语句包含在pthread_cleanup_push()/pthread_cleanup_pop()对中,则不会引起清理函数的执行,反而会导致segment fault。
  • 相关阅读:
    【转】关于GRIB数据的处理
    [转载]国际gis遥感杂志
    【转】linux后台运行和关闭、查看后台任务
    OpenXML: excel 插入BarChart图表
    利用.Net Framework2.0 zip压缩、解压 string 数据
    C# 常见面试题(2)
    'String or binary data would be truncated' error message
    Openxml: 导出excel 设置 cell的格式
    OpenXML: Asp.net利用OpenXML 导出Excel.
    C#中文和UNICODE字符转换方法
  • 原文地址:https://www.cnblogs.com/yuanqiangfei/p/15409038.html
Copyright © 2020-2023  润新知