终止线程执行,千万别踩这个坑!

终止线程执行,千万别踩这个坑!

c语言中文网:终止线程执行,千万别踩这个坑!

使用 pthread_cancel()函数,有时候会发生并没有cancel掉子线程的情况,子线程仍然继续运行。

在《终止线程执行(3种方法)》一节中,我们对 pthread_cancel() 函数的功能和用法做了详细的介绍。总的来说,通过调用 pthread_cancel() 函数,一个线程可以向同进程内的另一个线程发送“终止执行”的信号(Cancel 信号),使目标线程结束执行。

实际使用 pthread_cancel() 函数时,很多读者会发现“Cancel 信号成功发送,但目标线程并未立即终止执行”等类似的问题举个例子,在 Linux 环境中执行如下程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
void * thread_Fun(void * arg) {
printf("新建线程开始执行\n");
//插入无限循环的代码,测试 pthread_cancel()函数的有效性
while(1);
}
int main()
{
pthread_t myThread;
void * mess;
int value;
int res;
res = pthread_create(&myThread, NULL, thread_Fun, NULL);
if (res != 0) {
printf("线程创建失败\n");
return 0;
}
sleep(1);
//令 myThread 线程终止执行
res = pthread_cancel(myThread);
if (res != 0) {
printf("终止 myThread 线程失败\n");
return 0;
}
printf("等待 myThread 线程执行结束:\n");
res = pthread_join(myThread, &mess);
if (res != 0) {
printf("等待线程失败\n");
return 0;
}
if (mess == PTHREAD_CANCELED) {
printf("myThread 线程被强制终止\n");
}
else {
printf("error\n");
}
return 0;
}

假设程序编写在 thread.c 文件中,执行过程如下:

1
2
3
4
5
[root@localhost ~]# gcc thread.c -o thread.exe -lpthread
[root@localhost ~]# ./thread.exe
新建线程开始执行
等待 myThread 线程执行结束:

myThread 线程被强制终止这行并没有出来,就卡在res = pthread_join(myThread, &mess);这步,子线程一直在运行,说明并没有cancel掉。

程序中,主线程( main() 函数)试图调用 pthread_cancel() 函数终止 myThread 线程执行。从运行结果不难发现,pthread_cancel() 函数成功发送了 Cancel 信号,但目标线程仍在执行。

也就是说,==接收到 Cancel 信号的目标线程并没有立即处理该信号,或者说目标线程根本没有理会此信号。==解决类似的问题,我们就需要搞清楚目标线程对 Cancel 信号的处理机制。

线程对Cancel信号的处理

对于默认属性的线程,当有线程借助 pthread_cancel() 函数向它发送 Cancel 信号时,它并不会立即结束执行,而是选择在一个适当的时机结束执行。

所谓适当的时机,POSIX 标准中规定,当线程执行一些特殊的函数时,会响应 Cancel 信号并终止执行,比如常见的 pthread_join()、pthread_testcancel()、sleep()、system() 等,POSIX 标准称此类函数为“cancellation points”(中文可译为“取消点”)。

POSIX 标准中明确列举了所有可以作为取消点的函数,这里不再一一罗列,感兴趣的读者可以自行查阅 POSIX 标准手册。

此外,<pthread.h> 头文件还提供有 pthread_setcancelstate() 和 pthread_setcanceltype() 这两个函数,我们可以手动修改目标线程处理 Cancel 信号的方式。

1、pthread_setcancelstate()函数

借助 pthread_setcancelstate() 函数,我们可以令目标线程处理 Cancal 信号,也可以令目标线程不理会其它线程发来的 Cancel 信号。

pthread_setcancelstate() 函数的语法格式如下:

1
int pthread_setcancelstate( int state , int * oldstate ); 
  1. state 参数有两个可选值,分别是:

    • PTHREAD_CANCEL_ENABLE(默认值):当前线程会处理其它线程发送的 Cancel 信号;
    • PTHREAD_CANCEL_DISABLE:当前线程不理会其它线程发送的 Cancel 信号,直到线程状态重新调整为 PTHREAD_CANCEL_ENABLE 后,才处理接收到的 Cancel 信号。
  2. oldtate 参数用于接收线程先前所遵循的 state 值,通常用于对线程进行重置。如果不需要接收此参数的值,置为 NULL 即可。

pthread_setcancelstate() 函数执行成功时,返回数字 0,反之返回非零数。

2、pthread_setcanceltype()函数

当线程会对 Cancel 信号进行处理时,我们可以借助 pthread_setcanceltype() 函数设置线程响应 Cancel 信号的时机。

pthread_setcanceltype() 函数的语法格式如下:

1
int pthread_setcanceltype( int type , int * oldtype );
  1. type 参数有两个可选值,分别是:

    • PTHREAD_CANCEL_DEFERRED(默认值):当线程执行到某个可作为取消点的函数时终止执行;
    • PTHREAD_CANCEL_ASYNCHRONOUS:线程接收到 Cancel 信号后立即结束执行。
  2. oldtype 参数用于接收线程先前所遵循的 type 值,如果不需要接收该值,置为 NULL 即可。

pthread_setcanceltype() 函数执行成功时,返回数字 0,反之返回非零数。

接下来通过一个实例给大家演示以上两个函数的功能和用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
void * thread_Fun(void * arg) {
printf("新建线程开始执行\n");
int res;
//设置线程为可取消状态
res = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
if (res != 0) {
printf("修改线程可取消状态失败\n");
return NULL;
}
//设置线程接收到 Cancel 信号后立即结束执行
res = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
if (res != 0) {
printf("修改线程响应 Cancel 信号的方式失败\n");
return NULL;
}
while (1);
return NULL;
}
int main()
{
pthread_t myThread;
void * mess;
int value;
int res;
res = pthread_create(&myThread, NULL, thread_Fun, NULL);
if (res != 0) {
printf("线程创建失败\n");
return 0;
}
sleep(1);
//向 myThread 线程发送 Cancel 信号
res = pthread_cancel(myThread);
if (res != 0) {
printf("终止 myThread 线程失败\n");
return 0;
}
//等待 myThread 线程执行结束,获取返回值
res = pthread_join(myThread, &mess);
if (res != 0) {
printf("等待线程失败\n");
return 0;
}
if (mess == PTHREAD_CANCELED) {
printf("myThread 线程被强制终止\n");
}
else {
printf("error\n");
}
return 0;
}

假设程序编写在 thread.c 文件中,程序执行过程如下:

1
2
3
4
[root@localhost ~]# gcc thread.c -o thread.exe -lpthread
[root@localhost ~]# ./thread.exe
新建线程开始执行
myThread 线程被强制终止

和《终止线程执行(3种方法)》一节中 pthread_cancel() 函数的演示程序相比,我们仅仅是将 myThread 线程设置为“接收到 Cancel 信号后立即结束执行”。通过对比两个程序的输出结果,很容易就可以体会出 pthread_setcancelstate() 和 pthread_setcanceltype() 函数的功能。

其实就是在要用多线程的函数里多写几句话,设置线程为可取消状态、设置线程接收到 Cancel 信号后立即结束执行。