取消线程
取消操作允许线程请求终止其所在进程中的任何其他线程。不希望或不需要对一组相关的线程执行进一步操作时,可以选择执行取消操作。例如,用户请求关闭或退出正在运行的应用程序。另一个示例是完成由许多线程执行的任务。其中的某个线程可能最终完成了该任务,而其它线程还在继续运行。由于正在运行的线程此时没有任何用处,因此取消这个线程。
取消点
仅当取消操作安全时才应取消线程。pthreads标准指定了几个取消点,其中包括:
- 通过pthread_testcancel调用以编程方式建立线程取消点。
- 线程等待pthread_cond_wait或pthread_cond_timewait()中的特定条件。
- 被sigwait(2)阻塞的函数
- 一些标准的库调用。通常,这些调用包括线程可基于阻塞的函数。
缺省情况下,将启用取消功能。有时,您可能希望应用程序禁用取消功能。如果禁用取消功能,则会导致延迟所有的取消请求,直到再次启用取消请求。 根据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()调用。
放置取消点
执行取消操作存在一定的危险。大多数危险都与完全恢复不变量和释放共享资源有关。取消线程时一定要格外小心,否则可能会使互斥保留为锁定状态,从而导致死锁状态。或者,已取消的线程可能保留已分配的内存区域,但是系统无法识别这一部分内存,从而无法释放它。
标准C库指定了一个取消接口用于以编程方式允许或禁止取消功能。该库定义的取消点是一组可能会执行取消操作的点。该库还允许定义取消处理程序的范围,以确保这些处理程序在预期的时间和位置运行。取消处理程序提供的清理服务可以将资源和状态恢复到与起点一致的状态。
必须对应用程序有一定的了解,才能放置取消点并执行取消处理程序。互斥肯定不是取消点,只应当在必要时使之保留尽可能短的时间。请将异步取消区域限制在没有外部依赖性的序列,因为外部依赖性可能会产生挂起的资源或未解决的状态条件。在从某个备用的嵌套取消状态返回时,一定要小心地恢复取消状态。该接口提供便于进行恢复的功能:pthread_setcancelstate(3C) 在所引用的变量中保留当前的取消状态,pthread_setcanceltype(3C) 以同样的方式保留当前的取消类型。
在以下三种情况下可能会执行取消操作:
- 异步
- 执行序列中按照标准定义的点
- 调用pthread_cancel()
取消线程方面的函数
int pthread_cancel(pthread_t thread);
返回:
成功之后返回0。失败返回错误号,错误号说明如下:
ESRCH:没有找到线程ID相对应的线程。
int pthread_setcancelstate(int state, int *oldstate);设置本线程对信号的反应
状态有两种:
PTHREAD_CANCEL_ENABLE 默认,收到cancel信号马上设置退出状态。
PTHREAD_CANCEL_DISABLE 收到cancel信号继续运行。
返回:
成功之后返回0。失败返回错误号,错误号说明如下:
EINVAL:状态不是PTHREAD_CANCEL_ENABLE或者PTHREAD_CANCEL_DISABLE
int pthread_setcanceltype(int type, int *oldtype);
类型有两种,只有在PTHREAD_CANCEL_ENABLE状态下有效
PTHREAD_CANCEL_ASYNCHRONOUS 立即执行取消信号
PTHREAD_CANCEL_DEFERRED 运行到下一个取消点
返回:
成功之后返回0.失败返回错误号,错误号说明如下:
EINVAL:状态不是PTHREAD_CANCEL_ASYNCHRONOUS或者PTHREAD_CANCEL_DEFERRED
void pthread_testcancel(void);
当线程取消功能处于启用状态且取消状态设置为延迟状态时,pthread_testcancel()函数有效。如果在取消功能处处于禁用状态下调用pthread_testcancel(),则该函数不起作用。
请务必仅在线程取消线程操作安全的序列中插入pthread_testcancel()。除通过pthread_testcancel()调用以编程方式建立的取消点意外,pthread标准还指定了几个取消点。
测试退出点,就是测试cancel信号
线程取消点例子
l 线程中没有取消点
(线程中是一个死循环,循环中没有取消点,在主程序中调用pthread_cancel对子线程没有影响)
1
#include
<
pthread.h
>
2
3
#include
<
stdio.h
>
4
5
#include
<
unistd.h
>
6
7
8
9
void
*
thr(
void
*
arg)
10
11
{
12
13 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
14
15 pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
16
17
18
19 while(1)
20
21 {
22
23 ;
24
25 }
26
27 printf("thread is not running\n");
28
29 sleep(2);
30
31}
32
33
34
35
int
main()
36
37
{
38
39 pthread_t th1;
40
41 int err;
42
43 err = pthread_create(&th1,NULL,thr,NULL);
44
45 pthread_cancel(th1);
46
47 pthread_join(th1,NULL);
48
49 printf("Main thread is exit\n");
50
51 return 0;
52
53}
54
l 子线程中有取消点
(printf系统调用可引起阻塞,是系统默认的取消点,但是最好是在其前后加pthread_testcancel()函数)
1
#include
<
pthread.h
>
2
3
#include
<
stdio.h
>
4
5
#include
<
unistd.h
>
6
7
8
9
void
*
thr(
void
*
arg)
10
11
{
12
13 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
14
15 pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
16
17
18
19 while(1)
20
21 {
22
23 printf("thread is running\n");
24
25 }
26
27 printf("thread is not running\n");
28
29 sleep(2);
30
31}
32
33
34
35
int
main()
36
37
{
38
39 pthread_t th1;
40
41 int err;
42
43 err = pthread_create(&th1,NULL,thr,NULL);
44
45 pthread_cancel(th1);
46
47 pthread_join(th1,NULL);
48
49 printf("Main thread is exit\n");
50
51 return 0;
52
53}
54
55
l 异步取消
(在异步取消时,线程不会去寻找取消点,而是立即取消)
1
#include
<
pthread.h
>
2
3
#include
<
stdio.h
>
4
5
#include
<
unistd.h
>
6
7
8
9
void
*
thr(
void
*
arg)
10
11
{
12
13 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
14
15 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
16
17
18
19 while(1)
20
21 {
22
23 ;
24
25 }
26
27 printf("thread is not running\n");
28
29 sleep(2);
30
31}
32
33
34
35
int
main()
36
37
{
38
39 pthread_t th1;
40
41 int err;
42
43 err = pthread_create(&th1,NULL,thr,NULL);
44
45 pthread_cancel(th1);
46
47 pthread_join(th1,NULL);
48
49 sleep(1);
50
51 printf("Main thread is exit\n");
52
53 return 0;
54
55}
56
57
l 设置不可取消
(不可取消)
l 设置取消点
1
#include
<
pthread.h
>
2
3
#include
<
stdio.h
>
4
5
#include
<
unistd.h
>
6
7
8
9
void
*
thr(
void
*
arg)
10
11
{
12
13 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
14
15 pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
16
17
18
19 while(1)
20
21 {
22
23 ;
24
25 pthread_testcancel();
26
27 }
28
29 printf("thread is not running\n");
30
31 sleep(2);
32
33}
34
35
36
37
int
main()
38
39
{
40
41 pthread_t th1;
42
43 int err;
44
45 err = pthread_create(&th1,NULL,thr,NULL);
46
47 pthread_cancel(th1);
48
49 pthread_join(th1,NULL);
50
51 sleep(1);
52
53 printf("Main thread is exit\n");
54
55 return 0;
56
57}
58
线程终止时的清理
不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证线程终止时能顺利的释放掉自己所占用的资源,特别是锁资源,就是一个必须考虑解决的问题。
最经常出现的情形是资源独占锁的使用:线程为了访问临界资源而为其加上锁,但在访问过程中被外界取消,如果线程处于响应取消状态,且采用异步方式响应,或者在打开独占锁以前的运行路径上存在取消点,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于资源释放的编程。
在POSIX线程API中提供了一个pthread_cleanup_push()/pthread_cleanup_pop()函数对用于自动释放资源--从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用pthread_exit()和取消点终止)都将执行pthread_cleanup_push()所指定的清理函数。