关于pthread_cancel
关于pthread_cancel
软件版本:
操作系统:ubuntu10.04
内核版本:Linux version 2.6.32-36-generic
目录:
1. 线程终止方式
2. pthread_cancel 请求退出
3. 由 pthread_cancel 引起的死锁问题
4. 关于 pthread_cancel 取消点
5. 参考资料
1. 线程终止方式
线程可能的终止方式包括:
· return 从启动例程中返回,返回值就是线程的退出码。进程中的其它线程可通过 pthread_join 函数获取这个返回值。
· void pthread_exit(void *rval_ptr); 退出线程,进程中的其它线程可通过 pthread_join 函数访问到 rval_ptr 这个指针。
· exit 、_Exit 或 _exit 。进程中的任一线程调用该函数,则终止整个进程。请慎用。
· int pthread_cancel(pthread_t tid); 请求同一进程中的其它线程退出。要注意的是,该函数并不等待线程终止,他仅仅提出请求。调用了该函数也不等于目标线程马上就会退出,目标线程有可能再运行一段时间后到达取消点才退出;甚至有可能不响应退出。
· int pthread_join(pthread_tthread, void **rval_ptr); 调用线程将一直阻塞,直到指定线程退出。如果目标线程处于分离状态时,pthread_join 马上返回 EINVAL 。
本文的主要目的想介绍一下关于 pthread_cancel 的一些需要注意的地方。
2. pthread_cancel 请求退出
基本用法:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void* thread(void* data)
{
printf("thread start......\n");
while(1)
{
printf("thread running...\n");
sleep(1);
}
printf("thread exit...\n");
pthread_exit(NULL);
}
int main()
{
pthread_t tid;
if (pthread_create(&tid, NULL, thread, NULL) != 0)
{
exit(1);
}
sleep(5); // sleep a while.
pthread_cancel(tid);
pthread_join(tid, NULL);
printf("main thread exit...\n");
return 0;
}
运行后的打印:
$./main
thread start......
thread running...
thread running...
thread running...
thread running...
thread running...
main thread exit...
新建的线程如我们所愿的退出了,但是从打印来看,thread 并没有执行最后的 printf 函数,也就是说线程非正常退出。那么如果在线程刚开始的时候申请了一些系统资源,该如何释放呢?这时就需要用到线程清理处理程序。
#include <pthread.h>
void pthread_cleanup_push(void (*rtn)(void *), void *arg);
void pthread_cleanup_pop(int execute);
关于这对函数,要注意的是他们必须成对出现。否则就会编译出错,而且错误比较奇怪。这是由于他的定义所造成的,喜欢刨根问底的可以去看一下 pthread.h 这个头文件中关于该函数的定义,这里我就不再展开了。
运用 cleanup 释放资源的 demo 。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
void cleanup_function(void *data)
{
printf("run thread cleanup function.\n");
}
void* thread(void* arg)
{
printf("thread start......\n");
pthread_cleanup_push(cleanup_function, NULL);
while(1)
{
printf("thread running...\n");
sleep(1);
}
printf("thread exit...\n");
pthread_cleanup_pop(0);
pthread_exit(NULL);
}
int main(int argc, char **argv)
{
pthread_t tid;
if (pthread_create(&tid, NULL, thread, NULL) != 0)
{
exit(1);
}
sleep(5); // sleep a while.
pthread_cancel(tid);
pthread_join(tid, NULL);
printf("main thread exit...\n");
return 0;
}
将上面的代码编译运行后,打印为:
$./main
thread start......
thread running...
thread running...
thread running...
thread running...
thread running...
run thread cleanup function.
main thread exit...
从打印可以看出,thread 在退出前调用了 cleanup 函数。
3. 由 pthread_cancel 引起的死锁问题
程序在运行的过程中突然间被取消掉,可能引发的问题有许多,其中之一就是死锁的问题。下面这段代码就是一个引起死锁的例子[2]。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void* thread_0(void* data)
{
pthread_mutex_lock(&mutex);
printf("thread_0: 1\n");
pthread_cond_wait(&cond, &mutex);
printf("thread_0: 2\n");
pthread_mutex_unlock(&mutex);
printf("thread_0: 3\n");
pthread_exit(NULL);
}
void* thread_1(void* data)
{
sleep(5);
printf("thread_1: 1\n");
pthread_mutex_lock(&mutex);
printf("thread_1: 2\n");
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
printf("thread_1: 3\n");
pthread_exit(NULL);
}
int main()
{
pthread_t tid[2];
if (pthread_create(&tid[0], NULL, thread_0, NULL) != 0)
{
exit(1);
}
if (pthread_create(&tid[1], NULL, thread_1, NULL) != 0)
{
exit(1);
}
sleep(2);
printf("main: 1\n");
pthread_cancel(tid[0]);
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
printf("main: return \n");
return 0;
}
该程序运行时会产生死锁,产生死锁的原因就是因为线程0被取消后,没有执行 pthread_mutex_unlock(&mutex); 。导致线程1一直获取不到 mutex 而产生死锁现象。
这个例子提出了一个值得我们思考的问题,就是到底线程什么时候会响应 pthread_cancel 的请求而退出?下面来介绍一下取消选项。
4. 关于 pthread_cancel 取消点
首先我们要清楚明白的是 pthread_cancel 只是提出取消请求。至于如何处理这个 cancel 信号则由线程自己决定,可以响应取消,也可以不响应取消;可以马上响应,也可以延时处理。
在 pthread_attr_t 线程属性结构中,有两个线程属性没有被包含其中,他们分别是可取消状态(PTHREAD_CANCEL_ENABLE/PTHREAD_CANCEL_DISABLE)和可取消类型(PTHREAD_CANCEL_DEFERRED/PTHREAD_CANCEL_ASYNCHRONOUS)。
线程启动时的默认可取消状态为 PTHREAD_CANCEL_ENABLE 。可通过调用函数 pthread_setcancelstate 修改。
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
线程启动时的默认可取消类型为 PTHREAD_CANCEL_DEFERRED 。可通过调用函数 pthread_setcanceltype 修改。
#include <pthread.h>
int pthread_setcanceltype(int type, int *oldtype);
当线程的可取消状态为 PTHREAD_CANCEL_ENABLE 且可取消类型为延时取消(PTHREAD_CANCEL_DEFERRED)时,线程在取消请求发出以后还是继续运行,直到到达某个取消点。取消点就是线程检查是否被取消并按照请求进行动作的一个位置。如果想了解取消点有哪些可以参考《UNIX 环境高级编程》第411页表格12-7与12-8。由于函数非常多,我就不一一列出来了。其中死锁例子中用到的 pthread_cond_wait 就是一个可取消点。
当线程的可取消状态为 PTHREAD_CANCEL_ENABLE 且可取消类型为异步取消(PTHREAD_CANCEL_ASYNCHRONOUS)时,线程可以在任意时刻取消,而不是非得遇到取消点才能被取消。
当线程的可取消状态为 PTHREAD_CANCEL_DISABLE 时,线程不响应 cancel 信号。
现在我们来将上一节中的死锁例子的线程0的可取消状态设置为 PTHREAD_CANCEL_DISABLE 。看会有什么样的结果。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void* thread_0(void* data)
{
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
pthread_mutex_lock(&mutex);
printf("thread_0: 1\n");
pthread_cond_wait(&cond, &mutex);
printf("thread_0: 2\n");
pthread_mutex_unlock(&mutex);
printf("thread_0: 3\n");
pthread_exit(NULL);
}
void* thread_1(void* data)
{
sleep(5);
printf("thread_1: 1\n");
pthread_mutex_lock(&mutex);
printf("thread_1: 2\n");
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
printf("thread_1: 3\n");
pthread_exit(NULL);
}
int main()
{
pthread_t tid[2];
if (pthread_create(&tid[0], NULL, thread_0, NULL) != 0)
{
exit(1);
}
if (pthread_create(&tid[1], NULL, thread_1, NULL) != 0)
{
exit(1);
}
sleep(2);
printf("main: 1\n");
pthread_cancel(tid[0]);
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
printf("main: return \n");
return 0;
}
编译运行后的打印:
$./main
thread_0: 1
main: 1
thread_1: 1
thread_1: 2
thread_1: 3
thread_0: 2
thread_0: 3
main: return
可以看到程序并没有产生死锁。其实就是由于线程0设置为不响应 cancel 信号,所以才能够正常返回。
5. 参考资料
[1] 《UNIX 环境高级编程》
[2] http://www.cnblogs.com/mydomain/archive/2011/08/15/2139830.html
[3] http://www.cnblogs.com/mydomain/archive/2011/08/15/2139850.html