概念
线程取消的方法是向目标线程发送 Cancel 信号,但如何处理 Cancel 信号则由目标线程自己决定,需要注意的是,线程取消是一个复杂的过程,需要考虑资源的一致性问题。
取消一个线程要确保该线程能够释放其所持有的任何锁、分配的内存,使整个系统保持一致性。在很多复杂情况下要保证这种正确性是有一定困难的。
一种简单的线程取消:取消线程调用一个取消线程的函数,被取消线程死亡。在这种情况下,被取消线程所持有的资源得不到释放。取消线程负责保证被取消者处于可安全取消状态,在一个要求可靠性高的系统中,这种保证非常困难或者无法实现。这种取消称为不受限制的异步取消。
与异步取消相关的是异步取消安全,即一段代码执行期间可以在任意点上被取消而不会引起不一致。满足该条件的函数称为异步取消安全函数。显然,异步取消安全函数不涉及使用互斥锁等。POSIX 标准要求这些函数是异步取消安全函数:pthread_cancel、pthread_setcancelstate、pthread_setcanceltype。
POSIX 标准定义了一种更安全的线程取消机制。一个线程可以以可靠的受控制的方式向进程其他线程发出取消请求,目标线程可以挂起这一请求使实际的取消动作在此后安全的时候进行,称为延迟取消。目标线程还可以定义其被取消后自动被系统调用的线程清除函数。
和延迟取消相关的一个概念是取消点。POSIX 通过为线程定义可取消状态,取消点函数以及一系列取消控制函数实现延迟取消。
线程可以调用 pthread_cancel 函数请求取消一个线程。
/*********************************************************************************************************
** 函数名称: pthread_cancel
** 功能描述: cancel 一个 posix 线程.
** 输 入 : thread 线程 id.
** 输 出 : ERROR CODE
*********************************************************************************************************/
int pthread_cancel (pthread_t thread)
消状态
线程取消状态决定了指定线程是否可以被取消,取消状态分为允许取消和禁止取消。一个线程设置为禁止取消意味着此线程只能从自己返回或者调用 pthread_exit函数来退出。
取消状态 | 说明 |
---|---|
LW_THREAD_CANCEL_ENABLE | 允许取消 |
LW_THREAD_CANCEL_DISABLE | 禁止取消 |
#include <pthread.h>
int pthread_setcancelstate(int newstate, int *poldstate);
函数 pthread_setcancelstate 原型分析:
- 此函数成功返回 0,失败返回错误号;
- 参数 newstate 是新状态,如表 6.4 所示;
- 输出参数 poldstate 返回之前的状态。
调用 pthread_setcancelstate 函数指定 newstate 参数值为LW_THREAD_CANCEL_ENABLE 来允许取消,值为 LW_THREAD_CANCEL_DISABLE 来禁止取消,如果参数 poldstate 非 NULL,则返回之前的取消状态。
取消类型
取消类型是线程取消的方式,分为异步取消和延迟取消。
取消类型 | 说明 |
---|---|
LW_THREAD_CANCEL_ASYNCHRONOUS | 异步取消 |
LW_THREAD_CANCEL_DEFERRED | 延迟取消 |
#include <pthread.h>
int pthread_setcanceltype(int newtype, int *poldtype);
函数 pthread_setcancelstate 原型分析:
- 此函数成功返回 0,失败返回非 0 值;
- 参数 newtype 是新类型,如表 6.5 所示;
- 输出参数 poldtype 返回之前的类型。
调用 pthread_setcanceltype 函数可以设置取消类型,当 poldtype 为非 NULL 时,则返回之前的取消类型。当设置取消状态为禁止时,对该线程的取消请求将被忽略。当设置取消状态为允许时,如果收到取消请求,则系统的动作由所选择的取消类型决定。
- 异步取消,取消请求立即被执行;
- 延迟取消,取消请求被挂起,直到运行到下一个取消点才被执行。
取消点
在使用延迟取消机制时,一个线程在可以被取消的地方定义取消点,当收到取消请求时,被取消的线程执行到取消点时退出,或者在一个取消点调用被阻塞时退出。通过延迟取消,程序在进入临界区时不需要进行禁止/允许取消操作。
由于在延迟取消时必须在取消点才能被取消,这一限制可能使取消请求被挂起任意长时间。因此,如果某个调用可能使线程被阻塞或者进入某个较长时间的过程,POSIX 要求这些调用属于一个取消点,或者称这些调用为取消点调用,以防止取消请求陷入长时间等待。
如下表所示是 SylixOS 中拥有取消点的函数。
- Lw_Thread_Cond_Wait
- Lw_Thread_Join
- Lw_Thread_Start
- accept4
- aio_suspend
- close
- connect
- creat
- fcntl
- fdatasync
- fsync
- lockf
- mq_receive
- mq_reltimedreceive_np
- mq_reltimedsend_np
- mq_send
- mq_timedreceive
- mq_timedsend
- msgrcv
- msgsnd
- open
- pause
- pread
- pselect
- pthread_barrier_wait
- pthread_join
- pthread_testcancel
- pwrite
- read
- readv
- reclaimchild
- recv
- recvfrom
- recvmsg
- select
- sem_reltimedwait_np
- sem_timedwait
- sem_wait
- send
- sendmsg
- sigsuspend
- sigtimedwait
- sigwait
- sigwaitinfo
- sleep
- system
- tcdrain
- wait
- wait3
- wait4
- waitid
- waitpid
- write
- writev
示例
下面程序是一个线程延时取消的实例,程序创建线程 thread0,线程中设置取消类型为延时取消,因为 sleep 函数是一个拥有取消点的函数,所以程序每次运行到 sleep 函数都会检查线程是否有取消请求,当检查出有取消请求时,将在下一个取消点处,取消线程。
#include <pthread.h>
#include <stdio.h>
void *thread0 (void *arg)
{
int oldstate;
int oldtype;
pthread_setcancelstate(LW_THREAD_CANCEL_ENABLE, &oldstate);
pthread_setcanceltype(LW_THREAD_CANCEL_DEFERRED, &oldtype);
while (1) {
fprintf(stdout, "thread0 running...\n");
sleep(1);
}
return (NULL);
}
int main (int argc, char *argv[])
{
pthread_t tid;
int ret;
ret = pthread_create(&tid, NULL, thread0, NULL);
if (ret != 0) {
return (-1);
}
sleep(3);
pthread_cancel(tid);
pthread_join(tid, NULL);
fprintf(stdout, "thread0 cancel.\n");
return (0);
}
在 SylixOS Shell 下运行程序,从结果可以看出线程的取消情况。
# ./Thread_Delay_Cancellation
thread0 running...
thread0 running...
thread0 running...
thread0 cancel.
取消点函数都是直接或者间接调用 pthread_testcancel 函数实现的,因此目标线程也可以调用 pthread_testcancel 函数来检查取消请求。pthread_testcancel 和其他取消点调用不同之处在于该函数除了创建一个取消点以外什么也不做,即专门为响应取消请求服务。
如果 pthread_testcancel 函数没有检查到取消请求,则直接返回到调用者,当检查到有取消请求时将线程删除不再返回到调用者。