不建议使用取消接口来使线程退出
①向线程发送取消请求
pthread_cancel(pthread_t thread);
1.一个线程可以通过调用该函数向另一个线程发送取消请求。
2.如果成功,该函数返回0,否则将错误码返回。
3.这不是个阻塞型接口,发出请求后,函数就立刻返回了,而不会等待目标线程退出之后才返回。
4.函数的本质:会向目标线程发送一个SIGCANCEL(可靠信号)的信号,该信号就是6.4节“ 信号的分类 ”中提到的被NPTL征用的32号信号。
②设置线程能否被取消(在线程执行函数中设置)
int pthread_setcancelstate(int state, int *oldstate);
·PTHREAD_CANCEL_ENABLE
线程的默认取消状态是 PTHREAD_CANCEL_ENABLE
·PTHREAD_CANCEL_DISABLE
1.其他线程发送的SIGCANCEL信号被暂时挂起(而不是被抛弃),暂时不予处理。当线程状态设置为PTHREAD_CANCEL_ENABLE后, SIGCANCEL信号不在被挂起, 会加入到正常的信号队列中。
2.那么收到取消请求后,会发生什么?这取决于线程的取消类型。
③设置线程的取消类型
int pthread_setcanceltype(int type, int *oldtype);
·PTHREAD_CANCEL_ASYNCHRONOUS(异步取消) // 永远不要使用异步取消
即线程可能在任何时间点(可能是立即取消,但也不一定)取消线程。这种取消方式太粗暴,很容易造成后续的混乱。
·PTHREAD_CANCEL_DEFERRED(延迟取消)
1.新建线程的默认取消类型为"延迟取消"。
2.取消点: 就是对于某些函数,如果线程允许取消且取消类型是延迟取消,并且线程也收到了取消请求,
那么当执行到这些函数的时候,线程就可以退出了。==>这些函数称为"取消点"函数
3.通过man pthreads可以查询到这些取消点函数(有好几十个之多)。
4.线程执行到取消点,会自动处理取消请求,但是如果线程没有用到任何取消点函数。
1.为了应对这种场景,系统引入了pthread_testcancel函数,该函数一定是取消点。
2.编程者可以周期性地调用该函数,只要有取消请求,线程就能响应。
void pthread_testcancel(void);
5.如果线程被取消,并且其分离状态是可连接的,那么需要由其他线程对其进行连接(回收资源)。
6. pthread_join 函数的第二个参数会被置成 PTHREAD_CANCELED ,通过该值可以知道线程并不是“寿终正寝”,而是被其他线程取消而导致的退出。
④线程取消的弊端
1.线程取消是一种在线程的外部强行终止线程的执行做法
2.目标线程可能会持有互斥量、信号量或其他类型的锁,这时候如果收到取消请求,其他线程可能会死锁(被取消的线程未释放锁)
⑤异步取消安全函数 //即使执行异步取消也安然无恙的函数
pthread_cancel()
pthread_setcancelstate()
pthread_setcanceltype()
⑥编程人员应该遵循以下原则:
第一,轻易不要调用 pthread_cancel 函数,在外部杀死线程是很糟糕的做法,毕竟如果想通知目标线程退出,还可以采取其他方法。
第二,如果不得不允许线程取消,那么在某些非常关键不容有失的代码区域,暂时将线程设置成不可取消状态,退出关键区域之后,再恢复成可以取消的状态。
第三,在非关键的区域,也要将线程设置成延迟取消,永远不要设置成异步取消。
⑦示例代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <pthread.h>
static int CANCEL_POINT = 0;
void *runtime1(void *arg)
{
printf("runtime1: before\n");
sleep(1);
printf("runtime1: after\n"); // 这一句不会执行, 线程被强制取消了
pthread_exit(NULL);
}
void *runtime2(void *arg)
{
/* 设置线程的状态为不可取消 */
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
printf("runtime2: before\n");
sleep(1);
printf("runtime2: after\n"); // 这一句不会执行, 线程被强制取消了
pthread_exit(NULL);
}
void *runtime3(void *arg)
{
/* 设置线程的取消类型为"延时取消" */
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); // 永远不要使用异步取消: PTHREAD_CANCEL_ASYNCHRONOUS
//sleep(1); // sleep也是一个还原点函数, 所以不能用
CANCEL_POINT = 0x121; // 为什么不用printf? 因为printf也是一个还原点函数(执行man pthreads查看所有还原点函数)
pthread_testcancel(); // 该函数一定是一个还原点函数
pthread_exit(NULL);
}
void *runtime4(void *arg)
{
/* 设置线程的取消类型为"延时取消" */ // 永远不要使用异步取消: PTHREAD_CANCEL_ASYNCHRONOUS
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
/* 编写关键代码, 设置为不允许线程被取消 */
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); // 这时线程取消信号SIGCANCEL被挂起在队列中
printf("This is the key code\n"); // printf是一个取消点函数, 但是这个线程不会被取消
/* 关键代码写完后, 设置为线程允许被取消 */
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
// 该函数一定是一个还原点函数
pthread_testcancel(); /* 挂起SIGCANCEL信号被解除, 线程被取消 */
pthread_exit(NULL);
}
int main(int argc, char **argv)
{
pthread_t ptid1, ptid2, ptid3, ptid4;
void *pret;
void *pret2;
void *pret3;
void *pret4;
printf("\n------------------------1-pthread cancel test--------------------------------\n");
/* 创建的线程默认状态为允许取消, 取消类型为延迟取消: PTHREAD_CANCEL_ENABLE, PTHREAD_CANCEL_DEFERRED */
if (pthread_create(&ptid1, NULL, runtime1, NULL))
perror("pthread_create1 err");
pthread_cancel(ptid1); // 发送SIGCANCEL信号,强制取消线程runtime1
pthread_join(ptid1, &pret); // 如果线程被取消,并且其分离状态是可连接的(默认),那么需要由其他线程对其进行连接(回收资源)。
/* 如果线程被取消, 第二个参数被置为PTHREAD_CANCELED */
if (pret == PTHREAD_CANCELED)
printf("runtime1 canceled, It's not a normal exit\n");
else
printf("runtime1 normal exit\n");
printf("\n------------------------2-pthread cannot cancel test--------------------------------\n");
/* 创建的线程默认状态为允许取消, 取消类型为延迟取消: PTHREAD_CANCEL_ENABLE, PTHREAD_CANCEL_DEFERRED */
if (pthread_create(&ptid2, NULL, runtime2, NULL))
perror("pthread_create2 err");
pthread_cancel(ptid2); // 这个线程不会被取消成功, 因为线程设置了不可取消状态
pthread_join(ptid2, &pret2); // 如果线程被取消,并且其分离状态是可连接的(默认),那么需要由其他线程对其进行连接(回收资源)。
/* 如果线程被取消, 第二个参数被置为PTHREAD_CANCELED */
if (pret2 == PTHREAD_CANCELED)
printf("runtime2 canceled, It's not a normal exit\n");
else
printf("runtime2 normal exit\n");
printf("\n------------------------3-pthread cancel type test--------------------------------\n");
/* 创建的线程默认状态为允许取消, 取消类型为延迟取消: PTHREAD_CANCEL_ENABLE, PTHREAD_CANCEL_DEFERRED */
if (pthread_create(&ptid3, NULL, runtime3, NULL))
perror("pthread_create3 err");
pthread_cancel(ptid3); // 这个线程不会立即取消, 直到执行了"取消点"
pthread_join(ptid3, &pret3); // 如果线程被取消,并且其分离状态是可连接的(默认),那么需要由其他线程对其进行连接(回收资源)。
if (CANCEL_POINT == 0x121)
printf("pthread ptid3 cancel when across CANCEL_POINT\n"); // 线程3直到遇到了取消点,才被取消
/* 如果线程被取消, 第二个参数被置为PTHREAD_CANCELED */
if (pret3 == PTHREAD_CANCELED)
printf("runtime3 canceled, It's not a normal exit\n");
else
printf("runtime3 normal exit\n");
printf("\n------------------------4- write security code --------------------------------\n");
/* 创建的线程默认状态为允许取消, 取消类型为延迟取消: PTHREAD_CANCEL_ENABLE, PTHREAD_CANCEL_DEFERRED */
if (pthread_create(&ptid4, NULL, runtime4, NULL))
perror("pthread_create4 err");
pthread_cancel(ptid4); // 这个线程不会立即取消, 直到执行了"取消点"
pthread_join(ptid4, &pret4); // 如果线程被取消,并且其分离状态是可连接的(默认),那么需要由其他线程对其进行连接(回收资源)。
/* 如果线程被取消, 第二个参数被置为PTHREAD_CANCELED */
if (pret4 == PTHREAD_CANCELED)
printf("runtime4 canceled, It's not a normal exit\n");
else
printf("runtime4 normal exit\n");
return 0;
}
/* 执行结果:
book@gui_hua_shu:$ gcc pthread_cancel.c -pthread
book@gui_hua_shu:$ ./a.out
------------------------1-pthread cancel test--------------------------------
runtime1: before
runtime1 canceled, It's not a normal exit
------------------------2-pthread cannot cancel test--------------------------------
runtime2: before
runtime2: after
runtime2 normal exit
------------------------3-pthread cancel type test--------------------------------
pthread ptid3 cancel when across CANCEL_POINT
runtime3 canceled, It's not a normal exit
------------------------4- write security code --------------------------------
This is the key code
runtime4 canceled, It's not a normal exit
book@gui_hua_shu:/work/nfs_root/qt_fs_new/2system_pro/pthread$
*/