【线程退出】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)退出。\n",(unsigned int)pthread_self());
    pthread_exit((void *)0);
}

void *func2()/*5秒钟之后退出*/
{
    sleep(5);
    printf("线程2(ID:0x%x)退出。\n",(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的线程不存在或者已经退出。\n",(unsigned int)tid);
    else if(pthread_kill_err == EINVAL)
        printf("发送信号非法。\n");
    else
        printf("ID为0x%x的线程目前仍然存活。\n",(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\n");
  }
  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("清理函数\n");
}

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

void thread_fun(void*p)
{      p=malloc(20);
        pthread_cleanup_push(cleanup,p);
        printf("子线程\n");
        sleep(1);            //系统调用,用来响应pthread_cancel函数
        printf("是否杀死了线程\n");  //如果线程在上一句被杀死,这一句不会被打印
        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("子线程\n");
       sleep(1);            //系统调用,用来响应pthread_cancel函数
       printf("是否杀死了线程\n");  //如果线程在上一句被杀死,这一句不会被打印
       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。
posted @ 2021-10-14 22:47  轻轻的吻  阅读(4149)  评论(0编辑  收藏  举报