一个线程可以用pthread_cancel()来向一个指定的线程发送终止信号,接受终止信号的线程可以设置对此信号的响应:
(1)用pthread_setcancelstate()来决定是否响应该信号;
(2)用pthread_setcanceltype()来决定响应信号的方式(延迟响应/立即响应);
(3)用pthread_cleanup_push()和pthread_cleanup_pop()设置线程终止前的退出程序。
设置线程是否响应该信号
线程默认能响应pthread_cancel发来的信号,将线程设置为不响应该信号的话,用下面这个函数:
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
函数功能:设置线程是否响应pthread_cancel发来的信号
参数:
state-----> PTHREAD_CANCEL_ENABLE(能响应)/ PTHREAD_CANCEL_DISABLE(不响应)
oldstate----->设置为NULL的话,就不能保留线程终止前的状态
返回值:成功返回0;失败返回非0值。
test: 将线程设置为不响应该信号一段时间,再将线程设置为能响应该信号,查看结果
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
//int pthread_setcancelstate(int state, int *oldstate);
void *thread_func(void *arg)
{
//将线程设置为不相应pthread_cancel发来的信号
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
for(int i = 0; i < 5; ++i)
{
printf("%d rose\n", i + 1);
sleep(1);
//当打印了三次rose后,将该线程设置为响应pthread_cancel发来的终止信号
if(2 == i)
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
}
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
pthread_cancel(tid); //向线程发送终止信号
pthread_join(tid, NULL);
return 0;
}
结果分析:子线程收到了由主线程发来的终止信号,但是由于子线程被设置为了(PTHREAD_CANCEL_DISABLE)不响应终止信号,在for循环里打印3次rose后,子线程被设置为了能响应终止信号,那么子进程应该在此时终止,但是为什么打印了第4次rose呢?是因为线程默认是延迟响应pthread_cancel发来的信号,延迟的意思是:当线程接收到终止信号后,会继续执行,直到遇到一个取消点函数(在这个例子里,printf()就是一个取消点函数),执行这个取消点函数,才终止该线程。
设置线程响应信号的方式
int pthread_setcanceltype(int type, int *oldtype);
函数功能:设置线程对pthread_cancel发来的信号所做的响应方式
参数:
type----->PTHREAD_CANCEL_DEFERRED(取消请求被延迟,直到线程下一次调用作为取消点的函数,线程的默认响应方式就是延迟响应)/PTHREAD_CANCEL_ASYNCHRONOUS(立即响应终止信号)
oldtype----->保存之前的类型状态,不保存的话,设置为NULL
返回值:成功返回0;失败返回非0错误码。
取消点函数:fprintf()、fputc()、fputs()、sleep()、printf()、usleep()
在上一次测试中验证了线程默认是延迟响应终止信号;
test: 将线程设置为立即响应终止信号;
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
//int pthread_setcancelstate(int state, int *oldstate);
void *thread_func(void *arg)
{
//将线程设置为立即响应终止信号
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
//将线程设置为不相应pthread_cancel发来的信号
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
for(int i = 0; i < 5; ++i)
{
printf("%d rose\n", i + 1);
sleep(1);
//当打印了三次rose后,将该线程设置为响应pthread_cancel发来的终止信号
if(2 == i)
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
}
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
pthread_cancel(tid); //向线程发送终止信号
pthread_join(tid, NULL);
return 0;
}
结果分析:将线程设置为立即响应终止信号后,这次打印了三次rose,即再for循环里当线程被设置为响应终止信号后,立马就会执行终止信号,而不会延迟到下一个取消点函数后再去执行终止信号。
设置线程执行终止信号前的退出程序
#include <pthread.h>
void pthread_cleanup_push(void (*routine)(void *),void *arg);
函数功能:线程收到pthread_cancel信号终止前执行退出函数,这些函数被叫做例程函数,可以设置多个例程函数,多个函数被创建为例程函数的话,这些函数是以入栈的形式被设置
参数:
routine----->线程被终止前执行的函数
arg----->传递给routine函数的参数
void pthread_cleanup_pop(int execute);
函数功能:以出栈的形式按照先进后出依次执行例程函数
参数:
execute----->设置为0的话,不会执行例程函数;设置为非0值时,会先执行例程函数,再执行终止信号
这两个函数的特点:
(1)如果线程收到pthread_cancel()发来的终止信号,会立即执行例程函数,不会往下执行;
(2)如果线程没有收到pthread_cancel()发来的终止信号,当执行到pthread_cleanup_pop()函数时,会根据此函数的参数来决定是否执行例程函数;
(3)pthread_cleanup_push()和pthread_cleanup_pop()必须成对调用,因为这两个函数的实现使用宏定义来实现的,而且是嵌套的宏定义,如果个数不对等,编译器会给出关于大括号 { 的错误提示。
test:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <pthread.h>
// void pthread_cleanup_push(void (*routine)(void *),void *arg);
// void pthread_cleanup_pop(int execute);
//例程函数
void func1(void *arg)
{
printf("例程函数func1\n");
}
void func2(void *arg)
{
printf("例程函数func2\n");
}
void func3(void *arg)
{
printf("例程函数func3\n");
}
//线程函数
void* thread_func(void *arg)
{
//将3个历程函数压栈
pthread_cleanup_push(func1, NULL);
pthread_cleanup_push(func2, NULL);
pthread_cleanup_push(func3, NULL);
for(int i = 0; i < 5; ++i)
{
printf("线程函数thread_func\n");
sleep(1);
}
//将3个例程函数出栈
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
pthread_t tid;
if(pthread_create(&tid, NULL, thread_func, NULL) != 0)
{
perror("pthread_create");
return -1;
}
sleep(2); //主线程休眠2秒
pthread_cancel(tid); //向创建的线程发送终止信号
pthread_join(tid, NULL);
return 0;
}
结果分析:在主线程给子线程发送终止信号后,子线程会以出栈的形式去执行例程函数。