段时间内都不会调用前面两张图中的取消点函数,那么可以调用pthread_testcancel
在线程中添加自己的取消点。
调用pthread_testcancel时,如果有某个取消请求处于挂起状态,且可取消状态为ENABLE,那么线程就会被取消。
void pthread_testcancel();
使用线程取消的风险
当线程响应取消请求而终止时,主要面临的两大风险:
- 线程里面的锁可能没有unlock,有可能导致死锁
- 线程申请的资源(如堆内存)没有释放
下面是一段由pthread_cancel引起的死锁范例代码。
#include <pthread.h>
#include <stdio.h>
static pthread_cond_t cond;
static pthread_mutex_t mutex;
void *thread0(void *arg)
{
pthread_mutex_lock(&mutex);
printf("thread 0 lock sucess\n");
pthread_cond_wait(&cond, &mutex); //主线程发出取消请求时,thread1阻塞于slepp(2),thread0阻塞于此取消点,导致thread0未解锁mutex就终止
printf("thread 0 pthread_cond_wait return\n");
pthread_mutex_unlock(&mutex);
pthread_exit(0);
}
void *thread1(void *arg)
{
sleep(2);
printf("thread 1 start lock\n");
pthread_mutex_lock(&mutex); //thread0终止约1S后,thread1执行到此,由于mutex已加锁,也没有其他地方能够对其解锁,从而导致死锁
printf("thread 1 lock sucess\n");
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
pthread_exit(0);
}
int main()
{
pthread_t tid[2];
pthread_cond_init(&cond, NULL);
pthread_mutex_init(&mutex, NULL);
pthread_create(&tid[0], NULL, thread0, NULL);
pthread_create(&tid[1], NULL, thread1, NULL);
sleep(1);
pthread_cancel(tid[0]);
printf("main thread request cancel thread 0\n");
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
return 0;
}
线程清理程序
可以使用线程清理程序来解决线程取消的风险问题。线程可以安排它退出时需要调用的函数,这样的函数称为线程清理程序。
一个线程可以注册多个清理程序,处理程序记录在栈中,也就是说,它们的执行顺序和注册顺序是相反的。
void pthread_cleanup_push(void (*routine)(void *), void *arg);
void pthread_cleanup_pop(int execute);
当线程执行以下动作时,清理程序是由pthread_cleanup_push函数调度的,调用时只有一个参数arg:
- 调用pthread_exit结束线程
- 响应pthread_cancel取消请求
- 用非零execute参数调用pthread_cleanup_pop
注意:如果线程以return方式终止,线程清理程序不会被调用。
不管发生上述哪种情况,pthread_cleanup_pop都将删除上次pthread_cleanup_push登记的线程清理程序。
这两个函数有一个限制,由于它们经常实现为宏,所以必须在与线程启动例程相同的作用域中以配对的方式使用,否则,可能会产生编译错误。
回到线程取消的风险问题上来,我们只需要在线程清理程序中解锁和释放资源,并在线程启动例程的第一步就注册清理程序,
这样,当线程因响应取消请求而终止时,线程清理程序就会得以执行。
#include <pthread.h>
#include <stdio.h>
static pthread_cond_t cond;
static pthread_mutex_t mutex;
void cleanup(void *arg)
{
pthread_mutex_unlock(&mutex);
printf("mutex unlock in cleanup\n");
}
void *thread0(void *arg)
{
pthread_cleanup_push(cleanup, NULL); //注册线程清理程序进行解锁
pthread_mutex_lock(&mutex);
printf("thread 0 lock sucess\n");
pthread_cond_wait(&cond, &mutex); //主线程发出取消请求时,thread1阻塞于slepp(2),thread0阻塞于此取消点
printf("thread 0 pthread_cond_wait return\n");
pthread_mutex_unlock(&mutex);
pthread_cleanup_pop(0);
pthread_exit(0);
}
void *thread1(void *arg)
{
sleep(2);
printf("thread 1 start lock\n");
pthread_mutex_lock(&mutex); //thread0终止约1S后,thread1执行到此
printf("thread 1 lock sucess\n");
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
pthread_exit(0);
}
int main()
{
pthread_t tid[2];
pthread_cond_init(&cond, NULL);
pthread_mutex_init(&mutex, NULL);
pthread_create(&tid[0], NULL, thread0, NULL);
pthread_create(&ti