取消线程运行pthread_cancel/pthread_cleanup_push

取消线程运行

基本概念

线程正常终止的方法:

  • return从线程函数返回。
  • 通过调用函数pthread_exit使线程退出
  • 线程可以被同一进程中的其他线程取消。

一般情况下,线程在其主体函数退出的时候会自动终止,但同时也可以因为接收到另一个线程发来的终止(取消)请求而强制终止。

线程取消的方法是向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程自己决定,或者忽略(当禁止取消时)、或者立即终止(当在取消点 或异步模式下)、或者继续运行至Cancelation-point(取消点,下面将描述),总之由不同的Cancelation状态决定。

线程接收到CANCEL信号的缺省处理(即pthread_create()创建线程的缺省状态)是继续运行至取消点再处理(退出),或在异步方式下直接 退出。一个线程处理cancel请求的退出操作相当于pthread_exit(PTHREAD_CANCELED)。当然线程可以通过设置为 PTHREAD_CANCEL_DISABLE来拒绝处理cancel请求。线程的取消与线程的工作方式(joinable或detached)无关。

pthread_cancel调用并不等待线程终止,它只提出请求。线程在取消请求(pthread_cancel)发出后会继续运行,直到到达某个取消点(CancellationPoint)。取消点是线程检查是否被取消并按照请求进行动作的一个位置。

取消点

pthreads标准指定了几个取消点,其中包括:

  • 通过pthread_testcancel调用以编程方式建立线程取消点。
  • 线程等待pthread_cond_wait或pthread_cond_timewait()中的特定条件。
  • 被sigwait(2)阻塞的函数

一些标准的库调用。通常,这些调用包括线程可基于阻塞的函数。

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

与线程取消相关的pthread函数

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

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

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

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

int pthread_join(pthread_t thread, void **retval)
用来等待一个线程的结束,pthread_join的调用者将被挂起并等待thread线程终止。需要注意的是一个线程仅允许一个线程使用pthread_join等待它结束,并且被等待的线程应该处于可join状态。即非DETACHED状态。DETACHED是指某个线程执行pthread_detach后所处的状态。处于DETACHED状态的线程无法由pthread_join同步。一个可pthread_join的线程所占用的资源仅当有线程对其执行了pthread_join后才会释放,因此为了防止内存泄漏,所有线程终止时,要么已经被设置为DETACHED状态,要么使用pthread_join来回收资源。

示例

线程中没有取消点

线程中是一个死循环,循环中没有取消点,在主程序中调用pthread_cancel对子线程没有影响

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void* mythread(void* arg)
{
	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
	pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
	while(1)
	{
		;
	}
	printf("thread is not running\n");
	sleep(2);
}

int main()
{
	pthread_t t1;
	int err;
	err = pthread_create(&t1,NULL,mythread,NULL);
	pthread_cancel(t1);
	pthread_join(t1,NULL);
	printf("Main thread is exit\n");
	return 0;
}

线程中有取消点

printf系统调用可引起阻塞,是系统默认的取消点,但是最好是在其前后加pthread_testcancel()函数

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void* mythread(void* arg)
{
	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
	pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);

	while(1)
	{
		printf("thread is running\n");
	}
	printf("thread is not running\n");
	sleep(2);
}

int main()
{
	pthread_t t1;
	int err;
	err = pthread_create(&t1,NULL,mythread,NULL);
	pthread_cancel(t1);
	pthread_join(t1,NULL);
	printf("Main thread is exit\n");
	return 0;
}

异步取消

在异步取消时,线程不会去寻找取消点,而是立即取消

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void* mythread(void* arg)
{
	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
	pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
	while(1)
	{
		;
	}
	printf("thread is not running\n");
	sleep(2);
}
int main()
{
	pthread_t t1;
	int err;
	err = pthread_create(&t1,NULL,mythread,NULL);
	pthread_cancel(t1);
	pthread_join(t1,NULL);
	sleep(1);
	printf("Main thread is exit\n");
	return 0;
}

设置不可取消

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void* mythread(void* arg)
{
	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
	pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
	while(1)
	{   
		printf("thread is running\n");
	}
	printf("thread is not running\n");
	sleep(2);
}

int main()
{
	pthread_t t1;
	int err;
	err = pthread_create(&t1,NULL,mythread,NULL);
	pthread_cancel(t1);
	pthread_join(t1,NULL);
	sleep(1);
	printf("Main thread is exit\n");
	return 0;
}

设置取消点

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void* mythread(void* arg)
{
         pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
         pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
         while(1)
         {
                   ;
                   pthread_testcancel();
         }
         printf("thread is not running\n");
         sleep(2);
}

int main()
{
         pthread_t t1;
         int err;
         err = pthread_create(&t1,NULL,mythread,NULL);
         pthread_cancel(t1);
         pthread_join(t1,NULL);
         sleep(1);
         printf("Main thread is exit\n");
         return 0;
}

线程终止时的清理

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

最经常出现的情形是资源独占锁的使用:线程为了访问临界资源而为其加上锁,但在访问过程中被外界取消,如果线程处于响应取消状态,且采用异步方式响应,或者在打开独占锁以前的运行路径上存在取消点,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于资源释放的编程。

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

头文件:#include <pthread.h>
函数原型:void pthread_cleanup_push(void (*rtn)(void *), void *arg);
void pthread_clean_pop(int execute);
void(*rtn)(void *): 线程清理函数

当线程执行以下动作时,调用清理函数,调用的参数为arg,清理函数rtn的调用顺序是由pthread_cleanup_push函数来安排的。

  • 调用pthread_exit时
  • 响应取消请求时
  • 用非零execute参数调用pthread_cleanup_pop时
    补充:
    在线程宿主函数中主动调用return,如果return语句包含在pthread_cleanup_push()/pthread_cleanup_pop()对中,则不会引起清理函数的执行,反而会导致segment fault。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
 
void cleaner(void *arg)
{
    printf("cleanup:%s\n",(char*)arg);
}
void *thread1(void *arg)
{
    printf("thread 1 begin\n");
    pthread_cleanup_push(cleaner,"thread 1 11 handler");
    pthread_cleanup_push(cleaner,"thread 1 22 handler");
    printf("thread 1 push ok \n");
    if(arg)
        return ((void *)1);
    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);
    return ((void *)3);
}
void *thread2(void *arg)
{   
    printf("thread 2 begin\n");
    pthread_cleanup_push(cleaner,"thread 2 11 handler");
    pthread_cleanup_push(cleaner,"thread 2 22 handler");
    printf("thread 2 push ok \n");
    if(arg)
        pthread_exit((void *)2);
    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);
    pthread_exit((void *)4);
}
int main()
{
    int err;
    pthread_t t1,t2;
    void *tret;
    err = pthread_create(&t1,NULL,thread1,(void *)0);
    if(err != 0)
    {
        fprintf(stderr,"thread create 1 is error\n");
        return -1;
    }
    err = pthread_create(&t2,NULL,thread2,(void *)0);
    if(err != 0)
    {
        fprintf(stderr,"thread create 2 is error\n");
        return -2;
    }
    err = pthread_join(t1,&tret);
    if(err != 0)
    {
        fprintf(stderr,"can't join with thread 1\n");
        return -2;
    }
 
    //pthread_cancel(t1);
    printf("thread 1 exit code %d\n",tret);
    err = pthread_join(t2,&tret);
    if(err != 0)
    {
        fprintf(stderr,"can't join with thread 2\n");
        return -2;
    }
    printf("thread 2 exit code %d\n",tret);
    return 0;
}

输出

thread 1 begin
thread 1 push ok 
thread 1 exit code 3
thread 2 begin
thread 2 push ok 
thread 2 exit code 4

thread2没有调用清理函数,原因是最后一个pthread_exit之前调用了pthread_cleanup_pop把清理函数删除了
修改一下

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
 
void cleaner(void *arg)
{
    printf("cleanup:%s\n",(char*)arg);
}
void *thread1(void *arg)
{
    printf("thread 1 begin\n");
    pthread_cleanup_push(cleaner,"thread 1 11 handler");
    pthread_cleanup_push(cleaner,"thread 1 22 handler");
    printf("thread 1 push ok \n");
    if(arg)
        return ((void *)1);
    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);
    return ((void *)3);
}
void *thread2(void *arg)
{   
    printf("thread 2 begin\n");
    pthread_cleanup_push(cleaner,"thread 2 11 handler");
    pthread_cleanup_push(cleaner,"thread 2 22 handler");
    printf("thread 2 push ok \n");
    if(arg)
        pthread_exit((void *)2);
    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);
    pthread_exit((void *)4);
}
int main()
{
    int err;
    pthread_t t1,t2;
    void *tret;
    err = pthread_create(&t1,NULL,thread1,(void *)1);//修改点
    if(err != 0)
    {
        fprintf(stderr,"thread create 1 is error\n");
        return -1;
    }
    err = pthread_create(&t2,NULL,thread2,(void *)1);//修改点
    if(err != 0)
    {
        fprintf(stderr,"thread create 2 is error\n");
        return -2;
    }
    err = pthread_join(t1,&tret);
    if(err != 0)
    {
        fprintf(stderr,"can't join with thread 1\n");
        return -2;
    }
 
    //pthread_cancel(t1);
    printf("thread 1 exit code %d\n",tret);
    err = pthread_join(t2,&tret);
    if(err != 0)
    {
        fprintf(stderr,"can't join with thread 2\n");
        return -2;
    }
    printf("thread 2 exit code %d\n",tret);
    return 0;
}

输出

thread 2 begin
thread 2 push ok 
cleanup:thread 2 22 handler
cleanup:thread 2 11 handler
thread 1 begin
thread 1 push ok 
thread 1 exit code 3
thread 2 exit code 2

调用了清理函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值