7、取消点的进一步讨论
线程取消的方法是向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态决定。
线程接收到CANCEL信号的缺省处理(即pthread_create()创建线程的缺省状态)是继续运行至取消点,也就是说设置一个CANCELED状态,线程继续运行,只有运行至Cancelation-point的时候才会退出。
(1)什么是线程取消点
根据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);
(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)
Calling pthread_testcancel() creates a cancellation point within the calling thread, so that a thread that is otherwise executing code that contains no cancellation points will respond to a cancellation request.
If cancelability is disabled (using pthread_setcancelstate(3)), or no cancellation request is pending, then a call to pthread_cancel(3) has no effect.
也就是说pthread_testcancel在不包含取消点,但是又需要取消点的地方创建一个取消点,以便在一个没有包含取消点的执行代码线程中响应取消请求。
示例代码【3】
#include <pthread.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#define handle_error_en(en, msg) \
do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
static int done = 0;
static int cleanup_pop_arg = 0;
static int cnt = 0;
static void
cleanup_handler(void *arg)
{
printf("Called clean-up handler\n");
cnt = 0;
}
static void *
thread_start(void *arg)
{
time_t start, curr;
printf("New thread started\n");
pthread_cleanup_push(cleanup_handler, NULL);
curr = start = time(NULL);
while (!done) {
pthread_testcancel(); /* A cancellation point */
if (curr < time(NULL))
{
curr = time(NULL);
printf("cnt = %d\n", cnt); /* A cancellation point */
cnt++;
}
}
pthread_cleanup_pop(cleanup_pop_arg);
return NULL;
}
int
main(int argc, char *argv[])
{
pthread_t thr;
int s;
void *res;
s = pthread_create(&thr, NULL, thread_start, NULL);
if (s != 0)
handle_error_en(s, "pthread_create");
sleep(2); /* Allow new thread to run a while */
if (argc > 1)
{
if (argc > 2)
cleanup_pop_arg = atoi(argv[2]);
done = 1;
}
else
{
printf("Canceling thread\n");
s = pthread_cancel(thr);
if (s != 0)
handle_error_en(s, "pthread_cancel");
}
s = pthread_join(thr, &res);
if (s != 0)
handle_error_en(s, "pthread_join");
if (res == PTHREAD_CANCELED)
printf("Thread was canceled; cnt = %d\n", cnt);
else
printf("Thread terminated normally; cnt = %d\n", cnt);
exit(EXIT_SUCCESS);
}
示例代码跟踪
[root@localhost CFunctionTest]# ./thread
New thread started
cnt = 0
cnt = 1
Canceling thread
Called clean-up handler
Thread was canceled; cnt = 0
[root@localhost CFunctionTest]# gdb thread
(gdb) b 18
//...
(gdb) r
Starting program: /home/***/a***/CFunctionTest/thread
[Thread debugging using libthread_db enabled]
Breakpoint 6, main (argc=1, argv=0xbffff374) at testthread.cpp:54
54 s = pthread_create(&thr, NULL, thread_start, NULL);
Missing separate debuginfos, use: debuginfo-install glibc-2.12.90-17.i686 libgcc-4.5.1-4.fc14.i686 libstdc++-4.5.1-4.fc14.i686
(gdb) n
[New Thread 0xb7fecb70 (LWP 4845)]
New thread started
[Switching to Thread 0xb7fecb70 (LWP 4845)]
Breakpoint 2, thread_start (arg=0x0) at testthread.cpp:29
29 pthread_cleanup_push(cleanup_handler, NULL);
(gdb) n
31 curr = start = time(NULL);
(gdb) n
33 while (!done) {
(gdb) n
Breakpoint 3, thread_start (arg=0x0) at testthread.cpp:34
34 pthread_testcancel(); /* A cancellation point */
(gdb) n
Canceling thread
[Switching to Thread 0xb7fee6d0 (LWP 4840)]
Breakpoint 7, main (argc=1, argv=0xbffff374) at testthread.cpp:70
70 s = pthread_cancel(thr);
(gdb) n
cnt = 0
[Switching to Thread 0xb7fecb70 (LWP 4845)]
Breakpoint 4, thread_start (arg=0x0) at testthread.cpp:39
39 cnt++;
(gdb) n
33 while (!done) {
(gdb) n
Breakpoint 3, thread_start (arg=0x0) at testthread.cpp:34
34 pthread_testcancel(); /* A cancellation point */
(gdb) n
35 if (curr < time(NULL))
(gdb) n
37 curr = time(NULL);
(gdb) n
38 printf("cnt = %d\n", cnt); /* A cancellation point */
(gdb) n
cnt = 1
[Switching to Thread 0xb7fee6d0 (LWP 4840)]
Breakpoint 8, main (argc=1, argv=0xbffff374) at testthread.cpp:75
75 s = pthread_join(thr, &res);
(gdb) n
[Switching to Thread 0xb7fecb70 (LWP 4845)]
Breakpoint 4, thread_start (arg=0x0) at testthread.cpp:39
39 cnt++;
(gdb) n
33 while (!done) {
(gdb) n
Breakpoint 3, thread_start (arg=0x0) at testthread.cpp:34
34 pthread_testcancel(); /* A cancellation point */
(gdb) n
Breakpoint 1, cleanup_handler (arg=0x0) at testthread.cpp:18
18 printf("Called clean-up handler\n");
(gdb) n
Called clean-up handler
19 cnt = 0;
(gdb) n
20 }
(gdb) n
参考:
【1】 http://blog.csdn.net/yanook/article/details/6589798
【2】 http://blog.csdn.net/yulanarti/article/details/6197769
【3】 http://www.kernel.org/doc/man-pages/online/pages/man3/pthread_cleanup_push.3.html