关于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

posted @ 2012-04-01 16:46  Eddy_He  阅读(4038)  评论(0编辑  收藏  举报