pthread中取消线程

  取消线程:告诉一个线程关掉自己,取消操作允许线程请求终止其所在进程中的任何其他线程。不希望或不需要对一组相关的线程执行进一步操作时,可以选择执行取消操作。取消线程的一个示例是异步生成取消条件。

  对于cancel信号,线程有两种方法: 忽略,和响应。默认是响应
  接收到cancel信号,线程有两种处理类型: 立即响应 和 延迟响应(在最近的取消点响应),默认是延迟响应

//发送终止信号给thread线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止。
int pthread_cancel(pthread_t thread);

//设置本线程对Cancel信号的反应,state有两种值:PTHREAD_CANCEL_ENABLE(缺省)和PTHREAD_CANCEL_DISABLE,
//分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行;old_state如果不为NULL则存入原来的Cancel状态以便恢复。
int pthread_setcancelstate(int state,   int *oldstate);

//设置本线程取消动作的执行时机,type由两种取值:PTHREAD_CANCEL_DEFFERED和PTHREAD_CANCEL_ASYCHRONOUS,
//仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和立即执行取消动作(退出)
//oldtype如果不为NULL则存入运来的取消动作类型值。
int pthread_setcanceltype(int type, int *oldtype);

//pthread_testcancel在不包含取消点,但是又需要取消点的地方创建一个取消点,以便在一个没有包含取消点的执行代码线程中响应取消请求.
//线程取消功能处于启用状态且取消状态设置为延迟状态时,pthread_testcancel()函数有效。
//如果在取消功能处处于禁用状态下调用pthread_testcancel(),则该函数不起作用。
//请务必仅在线程取消线程操作安全的序列中插入pthread_testcancel()。除通过pthread_testcancel()调用以编程方式建立的取消点意外,
//pthread标准还指定了几个取消点。测试退出点,就是测试cancel信号.
void pthread_testcancel(void)

取消模式

模式状态类型含义
off禁用二者之一取消pending被推迟直到启用取消模式
deferred(推迟)启用推迟在下一个取消点执行取消
asynchronous(异步)启用异步可以随时执行取消
  1. 默认情况下取消被推迟,并且在程序的特定点发生,该点检查线程数是否被请求要求终止,被称为取消点。
  2. 可能等待一无界时间的大多数函数应该成为被推迟取消点。推迟的取消点包括等待条件变量,读写文件,以及线程可能被阻塞一段时间的其他函数
  3. 一个线程尚未解锁互斥量就被终止,试图加锁该互斥量的下一个线程永远被阻塞等待下去
  4. 从一个锁住互斥量的终止线程恢复数据的唯一办法:应用程序分析所有的共享数据并且恢复它到一个一致正确的状态
  5. 取消线程是一个异步的,当pthread_cancle返回时,线程未必已被取消,可能仅被通知有一个针对他的未解决的取消请求,如果需要知道线程何时终止,需要在取消他之后调用pthread_join与他连接
  6. 如果有一个异步取消类型集,或当线程下一次达到一个推迟的取消点时,取消请求被系统释放,发生这种情况,系统设置线程的取消类型为PTHREAD_CANCEL_DELIVEROD,取消状态为PTHREAD_CANCEL_DISABLE
  7. 当做为一个取消点函数检测到一个未解决的取消请求时,函数不返回调用者
  8. 如果目标线程从一个取消点返回,另外的线程针对目标现成调用了pthread_cancle,就存在一个未解决的取消,如果这一取消是未解决的,系统将很快开始调用清除函数,然后终止线程。
  9. 当异步取消开启时不允许调用任何获得资源的函数,因为异步取消能打断很多事。在异步取消后不要调用任何代码。

取消点

  1. 通过pthread_testcancel调用以编程方式建立线程取消点。 
  2. 线程等待pthread_cond_wait或pthread_cond_timewait()中的特定条件。 
  3. 被sigwait(2)阻塞的函数 
  4. 一些标准的库调用。通常,这些调用包括线程可基于阻塞的函数。 

注意:

  缺省情况下,将启用取消功能。有时,您可能希望应用程序禁用取消功能。如果禁用取消功能,则会导致延迟所有的取消请求,直到再次启用取消请求。  

  根据POSIX标准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及read()、write()等会引起阻塞的系统调用都是Cancelation-point,而其他pthread函数都不会引起Cancelation动作。但是pthread_cancel的手册页声称,由于LinuxThread库与C库结合得不好,因而目前C库函数都不是Cancelation-point;但CANCEL信号会使线程从阻塞的系统调用中退出,并置EINTR错误码,因此可以在需要作为Cancelation-point的系统调用前后调用pthread_testcancel(),从而达到POSIX标准所要求的目标。

pthread_testcancel();
retcode = read(fd, buffer, length);
pthread_testcancel();

  程序设计方面的考虑,如果线程处于无限循环中,且循环体内没有执行至取消点的必然路径,则线程无法由外部其他线程的取消请求而终止。因此在这样的循环体的必经路径上应该加入pthread_testcancel()调用.  

描述一下取消一个线程的过程:

其他线程通过调用pthread_cancel()函数向目标线程发送取消请求。取消请求发出后根据目标线程的cancel state来决定取消请求是否会到达目标线程:

  1. 如果目标线程的cancel state是PTHREAD_CANCEL_ENABLE(默认),取消请求会到达目标线程。
  2. 如果目标线程的cancel state是PTHREAD_CANCEL_DISABLE,取消请求会被放入队列。直到目标线程的cancel state变为PTHREAD_CANCEL_ENABLE,取消请求才会从队列里取出,发到目标线程。

取消请求到达目标线程后,根据目标线程的cancel type来决定线程何时取消:

  1.如果目标线程的cancel type是PTHREAD_CANCEL_DEFERRED(默认),目标线程并不会马上取消,而是在执行下一条cancellation point的时候才会取消。有很多系统函数都是cancellation point,

详细的列表可以在Linux上用man 7 pthreads查看。除了列出来的cancellation point,pthread_testcancel()也是一个cancellation point。就是说目标线程执行到pthread_testcancel()函数的时候,

 

如果该线程收到过取消请求,而且它的cancel type是PTHREAD_CANCEL_DEFERRED,那么这个线程就会在这个函数里取消(退出),这个函数就不再返回了,目标线程也没有了。

  2.如果目标线程的cancel type是PTHREAD_CANCEL_ASYNCHRONOUS(也就是异步取消),目标线程会立即取消(这里的“立即”只是说目标线程不用等执行到属于cancellation point的函数的时候才会取消,
  
它会在获得调度之后立即取消,因为内核调度会有延时,所以并不能保证时间上的“立即”)

void thread_function(void *arg)
{
/**
* 线程准备执行一些关键工作,在这个过程中不希望被取消。
* 所以先通过pthread_setcancelstate()将本线程的cancel state
* 设为disabled。
*/
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
/* 执行关键工作 */
...
/**
* 关键工作执行完成,可以被取消。
* 通过pthread_setcancelstate()将本线程的cancel state
* 设为enabled。
*/
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
/**
* 调用pthread_testcancel()函数,检查一下在cancel state
* 为disabled状态的时候,是否有取消请求发送给本线程。
* 如果有的话就取消(退出)。
*/
pthread_testcancel();
/**
* pthread_testcancel()返回了,表明之前没有取消请求发送给本线程,
* 继续其余的工作。
* 这时候如果有取消请求发送给本线程,会在下一次执行到
* cancellation point的时候(例如sleep(), read(), write(), ...)时取消。
*/
...
/**
* 从这里开始,函数里不再包含cancellation point了。
* 如果收到取消请求,将无法取消。所以先把本线程的cancel type
* 设为asynchronous,收到取消请求将立即取消。
*/
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
/* 不包含cancellation point的代码 */
...
}

 清除

  当一段代码被取消时需要恢复一些状态,必须使用清除处理器,当线程在等待一个条件变量时被取消,他将被唤醒,并保持互斥量的加锁状态,在线程终止前,通常需要恢复不变量,总是需要释放互斥来量。

  可以把每一个线程考虑为有一个活动的清除处理函数的栈,用pthread_cleanup_push将清除处理函数加入到栈,pthread_cleanup_pop删除最近添加的处理函数,当线程被取消或调用pthread_exit时,pthread从最近增加清除处理函数开始,依次调用各个活动的清除处理函数。

  清除处理函数也可以设计为线程被取消时,也能经常使用清除处理函数,不论取消是否正常完成,当pthread_cleanup_pop以非零值调用时就算线程没被取消,清除处理函数也要被执行

/* push the handler "routine" on cleanup stack */
void pthread_cleanup_push(void(*routine)(void *), void *args);

//如果弹出函数中的参数为非零值,则会从栈中删除该处理程序并执行该处理程序。如果该参数为零,则会弹出该处理程序,而不执行它。
void pthread_cleanup_pop(int execute);
/* pop the "func" out of cleanup stack and execute "func" */
pthread_cleanup_pop (1);
/* pop the "func" and DONT execute "func" */
pthread_cleanup_pop (0); 

code

    pthread_mutex_lock(&mutex);

    //一些会阻塞程序运行的调用,比如套接字的accept,等待客户连接,这里是随便找的一个可以阻塞的接口
    sock = accept(......);            

    pthread_mutex_unlock(&mutex);
    /*这个例子中,如果线程1执行accept时,线程会阻塞(也就是等在那里,有客户端连接的时候才返回,或则出现其他故障),线程等待中
    这时候线程2发现线程1等了很久,不赖烦了,他想关掉线程1,于是调用pthread_cancel()或者类似函数,请求线程1立即退出。
    这时候线程1仍然在accept等待中,当它收到线程2的cancel信号后,就会从accept中退出,然后终止线程,注意这个时候线程1还没有执行
    pthread_mutex_unlock(&mutex);也就是说锁资源没有释放,这回造成其他线程的死锁问题。*/

    /*所以必须在线程接收到cancel后用一种方法来保证异常退出(也就是线程没达到终点)时可以做清理工作(主要是解锁方面)
    pthread_cleanup_push与pthread_cleanup_pop就是这样的。*/

    pthread_cleanup_push(some_clean_func,...)
    pthread_mutex_lock(&mutex);
    //一些会阻塞程序运行的调用,比如套接字的accept,等待客户连接,里是随便找的一个可以阻塞的接口
    sock = accept(......);            这
    pthread_mutex_unlock(&mutex);
    pthread_cleanup_pop(0);
    return nullptr;
    
    /*上面的代码,如果accept被cancel后线程退出,会自动调用some_clean_func函数,在这个函数中你可以释放锁资源。
    如果accept没有被cancel,那么线程继续执行,当pthread_mutex_unlock(&mutex);表示线程自己正确的释放资源了,
    而执行pthread_cleanup_pop(0);也就是取消掉前面的some_clean_func函数。接着return线程就正确的结束了。*/
    
    //通俗点就是:
    /*pthread_cleanup_push注册一个回调函数,如果你的线程在对应的pthread_cleanup_pop之前异常退出(return是正常退出,
    其他是异常),那么系统就会执行这个回调函数(回调函数要做什么你自己决定)。但是如果在pthread_cleanup_pop之前没有异常退出,
    pthread_cleanup_pop就把对应的回调函数取消了,*/

    //关于取消点的解释:

    //比如你执行:
            printf("thread sleep\n");
            sleep(10);
            printf("thread wake...\n");
   // 在sleep函数中,线程睡眠,结果收到cancel信号,这时候线程从sleep中醒来,但是线程不会立刻退出。
   //这是应为pthread与C库方面的原因,pthread的建议是,如果一个函数是阻塞的,
   //那么你必须在这个函数前后建立取消点,比如:
            printf("thread sleep\n");
            pthread_testcancel();
            sleep(10);
            pthread_testcancel();
            printf("thread wake...\n");
    //这样,就添加了两个取消掉。在执行到pthread_testcancel的位置时,线程才可能响应cancel退出进程。

 

转载于:https://www.cnblogs.com/tianzeng/p/9195091.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值