取消线程

        在通常情况下,进程中的多个线程会并发执行,每个线程各司其职,直到线程的任务完成之后,该线程中会调用 pthread_exit()退出,或在线程 start 函数执行 return 语句退出。

有时候,在程序设计需求当中,需要向一个线程发送一个请求,要求它立刻退出,我们把这种操作称为取消线程,也就是向指定的线程发送一个请求,要求其立刻终止、退出。譬如,一组线程正在执行一个运算, 一旦某个线程检测到错误发生,需要其它线程退出,取消线程这项功能就派上用场了。下面就来讨论 Linux 系统下的线程取消机制。

取消一个线程

        通过调用 pthread_cancel()库函数向一个指定的线程发送取消请求,其函数原型如下所示:

#include <pthread.h>int pthread_cancel(pthread_t thread);

参数 thread 指定需要取消的目标线程;成功返回 0,失败将返回错误码。

发出取消请求之后,函数 pthread_cancel()立即返回,不会等待目标线程的退出。默认情况下,目标线程也会立刻退出,其行为表现为如同调用了参数为 PTHREAD_CANCELED(其实就是(void *)-1)的pthread_exit()函数,但是,线程可以设置自己不被取消或者控制如何被取消,所以 pthread_cancel()并不会等待线程终止,仅仅只是提出请求。

使用示例

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>static void *new_thread_start(void *arg){printf("新线程--running\n");for ( ; ; )sleep(1);return (void *)0;
}int main(void){pthread_t tid;void *tret;int ret;/* 创建新线程 */ret = pthread_create(&tid, NULL, new_thread_start, NULL);if (ret) {fprintf(stderr, "pthread_create error: %s\n", strerror(ret));exit(-1);}sleep(1);/* 向新线程发送取消请求 */ret = pthread_cancel(tid);if (ret) {fprintf(stderr, "pthread_cancel error: %s\n", strerror(ret));exit(-1);}/* 等待新线程终止 */ret = pthread_join(tid, &tret);if (ret) {fprintf(stderr, "pthread_join error: %s\n", strerror(ret));exit(-1);}printf("新线程终止, code=%ld\n", (long)tret);exit(0);
}

主线程创建新线程,新线程 new_thread_start()函数直接运行 for 死循环;主线程休眠一段时间后,调用 pthread_cancel()向新线程发送取消请求,接着再调用 pthread_join()等待新线程终止、获取其终止状态,将线程退出码打印出来。测试结果如下:

由打印结果可知,当主线程发送取消请求之后,新线程便退出了,而且退出码为-1,也就是 PTHREAD_CANCELED。

取消状态以及类型

默认情况下,线程会响应其它线程发送的取消请求的,响应请求然后退出线程。当然,线程可以选择不被取消或者设置取消方式,通过 pthread_setcancelstate()和 pthread_setcanceltype()来设置线程的取消性状态和类型。

#include <pthread.h>int pthread_setcancelstate(int state, int *oldstate);
int pthread_setcanceltype(int type, int *oldtype);

使用这些函数需要包含头文件,pthread_setcancelstate()函数会将调用线程的取消性状态设置为参数 state 中给定的值,并将线程之前的取消性状态保存在参数 oldstate 指向的缓冲区中,如果对之前的状态不感兴趣,Linux 允许将参数 oldstate 设置为 NULL;pthread_setcancelstate()调用成功将返回 0,失败返 回非 0 值的错误码。

pthread_setcancelstate()函数执行的设置取消性状态和获取旧状态操作记为一次原子操作。

参数 state 必须是以下值之一:

  1. PTHREAD_CANCEL_ENABLE:线程可以取消,这是新创建的线程取消性状态的默认值,所以新建线程以及主线程默认都是可以取消的。
  2. PTHREAD_CANCEL_DISABLE:线程不可被取消,如果此类线程接收到取消请求,则会将请求挂起,直至线程的取消性状态变为 PTHREAD_CANCEL_ENABLE。

使用示例

在新线程的 new_thread_start()函数中调用 pthread_setcancelstate()函数将线程的取消性状态设置为 PTHREAD_CANCEL_DISABLE,我们来试试,此时主线程还能不能取消新线程,示例代码如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>static void *new_thread_start(void *arg){/* 设置为不可被取消 */pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);for ( ; ; ) {printf("新线程--running\n");sleep(2);}return (void *)0;
}int main(void){pthread_t tid;void *tret;int ret;/* 创建新线程 */ret = pthread_create(&tid, NULL, new_thread_start, NULL);if (ret) {fprintf(stderr, "pthread_create error: %s\n", strerror(ret));exit(-1);}sleep(1);/* 向新线程发送取消请求 */ret = pthread_cancel(tid);if (ret) {fprintf(stderr, "pthread_cancel error: %s\n", strerror(ret));exit(-1);}/* 等待新线程终止 */ret = pthread_join(tid, &tret);if (ret) {fprintf(stderr, "pthread_join error: %s\n", strerror(ret));exit(-1);}printf("新线程终止, code=%ld\n", (long)tret);exit(0);
}

新线程 new_thread_start()函数中调用 pthread_setcancelstate()将自己设置为不可被取消,主线程延时 1 秒钟之后调用pthread_cancel()向新线程发送取消请求,那么此时新线程是不会终止的,pthread_cancel()立刻返回之后进入到 pthread_join()函数,那么此时会被阻塞等待新线程终止,接下来运行测试看看,结果会不会是这样:

测试结果确实如此,将一直重复打印"新线程--running",因为新线程是一个死循环(测试完成按 Ctrl+C 退出)。

pthread_setcanceltype()函数

如果线程的取消性状态为 PTHREAD_CANCEL_ENABLE,那么对取消请求的处理则取决于线程的取消性类型,该类型可以通过调用 pthread_setcanceltype()函数来设置,它的参数 type 指定了需要设置的类型, 而线程之前的取消性类型则会保存在参数 oldtype 所指向的缓冲区中,如果对之前的类型不敢兴趣,Linux 下允许将参数 oldtype 设置为 NULL。同样pthread_setcanceltype()函数调用成功将返回 0,失败返回非 0 值的错误码。

pthread_setcanceltype()函数执行的设置取消性类型和获取旧类型操作,这两步是一个原子操作。

参数 type 必须是以下值之一:

  1. ⚫ PTHREAD_CANCEL_DEFERRED:取消请求到来时,线程还是继续运行,取消请求被挂起,直到线程到达某个取消点为止,这是所有新建线程包括主线程默认的取消性类型。
  2. ⚫ PTHREAD_CANCEL_ASYNCHRONOUS:可能会在任何时间点(也许是立即取消,但不一定) 取消线程,这种取消性类型应用场景很少,不再介绍!

当某个线程调用 fork()创建子进程时,子进程会继承调用线程的取消性状态和取消性类型,而当某线程调用 exec 函 数 时 , 会将新程序主线程的取消性状态和类型重置为默认值 ,也就是 PTHREAD_CANCEL_ENABLE 和 PTHREAD_CANCEL_DEFERRED。

取消点

若将线程的取消性类型设置为 PTHREAD_CANCEL_DEFERRED 时(线程可以取消状态下),收到其它线程发送过来的取消请求时,仅当线程抵达某个取消点时,取消请求才会起作用。

那什么是取消点呢?所谓取消点其实就是一系列函数,当执行到这些函数的时候,才会真正响应取消请求,这些函数就是取消点;在没有出现取消点时,取消请求是无法得到处理的,究其原因在于系统认为,但没有到达取消点时,线程此时正在执行的工作是不能被停止的,正在执行关键代码,此时终止线程将可能会导致出现意想不到的异常发生。

取消点函数包括哪些呢?下表给大家简单地列出了一些:

         除了上表所列函数之外,还有大量的函数,系统实现可以将其作为取消点,这里便不再一一列举出来了,大家也可以通过 man 手册进行查询,命令为"man 7 pthreads",如下所示:

线程在调用这些函数时,如果收到了取消请求,那么线程便会遭到取消;除了这些作为取消点的函数之外,不得将任何其它函数视为取消点(亦即,调用这些函数不会招致取消)。

示例代码中,新线程处于 for 循环之中,调用 sleep()休眠,由表可知,sleep()函数可以作为取消点(printf 可能也是),当新线程接收到取消请求之后,会立马退出,但将代码修改为如下:

static void *new_thread_start(void *arg)
{printf("新线程--running\n");for ( ; ; ) {}return (void *)0;
}

那么线程将永远无法被取消,因为这里不存在取消点。大家可以将代码进行修改测试,看结果是不是如此!

线程可取消性的检测

假设线程执行的是一个不含取消点的循环(譬如 for 循环、while 循环),那么这时线程永远也不会响应取消请求,也就意味着除了线程自己主动退出,其它线程将无法通过向它发送取消请求而终止它,就如上小节最后给大家列举的例子。

在实际应用程序当中,确实会遇到这种情况,线程最终运行在一个循环当中,该循环体内执行的函数不存在任何一个取消点,但实际项目需求是:该线程必须可以被其它线程通过发送取消请求的方式终止,那这个时候怎么办?此时可以使用 pthread_testcancel(),该函数目的很简单,就是产生一个取消点,线程如果已有处于挂起状态的取消请求,那么只要调用该函数,线程就会随之终止。其函数原型如下所示:

#include <pthread.h>void pthread_testcancel(void);

功能测试

        接下来进行一个测试,主线程创建一个新的进程,新进程的取消性状态和类型置为默认,新进程最终执行的是一个不含取消点的循环;主线程向新线程发送取消请求,示例代码如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>static void *new_thread_start(void *arg){printf("新线程--start run\n");for ( ; ; ) {}return (void *)0;
}int main(void){pthread_t tid;void *tret;int ret;/* 创建新线程 */ret = pthread_create(&tid, NULL, new_thread_start, NULL);if (ret) {fprintf(stderr, "pthread_create error: %s\n", strerror(ret));exit(-1);}sleep(1);/* 向新线程发送取消请求 */ret = pthread_cancel(tid);if (ret) {fprintf(stderr, "pthread_cancel error: %s\n", strerror(ret));exit(-1);}/* 等待新线程终止 */ret = pthread_join(tid, &tret);if (ret) {fprintf(stderr, "pthread_join error: %s\n", strerror(ret));exit(-1);}printf("新线程终止, code=%ld\n", (long)tret);exit(0);
}

新线程的 new_thread_start()函数中是一个 for 死循环,没有执行任何函数,所以是一个没有取消点的循环体,主线程调用 pthread_cancel()是无法将其终止的。

执行完之后,程序一直会没有退出,说明主线程确实无法终止新线程。接下来再做一个测试,在 new_thread_start 函数的 for 循环体中执行 pthread_testcancel()函数,如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>static void *new_thread_start(void *arg){printf("新线程--start run\n");for ( ; ; ) {pthread_testcancel();}return (void *)0;
}int main(void){pthread_t tid;void *tret;int ret;/* 创建新线程 */ret = pthread_create(&tid, NULL, new_thread_start, NULL);if (ret) {fprintf(stderr, "pthread_create error: %s\n", strerror(ret));exit(-1);}sleep(1);/* 向新线程发送取消请求 */ret = pthread_cancel(tid);if (ret) {fprintf(stderr, "pthread_cancel error: %s\n", strerror(ret));exit(-1);}/* 等待新线程终止 */ret = pthread_join(tid, &tret);if (ret) {fprintf(stderr, "pthread_join error: %s\n", strerror(ret));exit(-1);}printf("新线程终止, code=%ld\n", (long)tret);exit(0);
}

如果 pthread_testcancel()可以产生取消点,那么主线程便可以终止新线程,测试结果如下:

从打印结果可知,确实如上面介绍那样,pthread_testcancel()函数就是取消点。

Linux线程(3)——pthread_cancel()取消一个线程相关推荐

  1. Java线程池线程突然没了_70%人答不全!线程池中的一个线程异常了会被怎么处理?...

    #线程池中的一个线程异常了会被怎么处理? 估计很多人会是以下三点答案(me too): 1.抛异常出来并打印在控制台上 2.其他线程任务不受影响 3.异常线程会被回收 但是这里我先提前说一下以上三点不 ...

  2. java 停止一个线程_Java如何停止一个线程

    线程正常执行完毕,正常结束. 2.监视某些条件,直到某些条件成立,结束线程. class TestMyThread extends Thread { private volatile boolean ...

  3. java线程池是如何复用线程_线程池如何复用一个线程-- ThreadPoolExecutor的实现(未完)...

    任务是一组逻辑工作单元,而线程则是使任务异步执行的机制.在Java中,Runnable对象代表一个任务,Thread对象负责创建一个线程执行这个任务. 前提:1. 程序需要处理大量任务 2. 任务的执 ...

  4. java如何创建一个两个数的队列_java线程池 如何构建一个线程立即到拉到MAX数量跑业务,线程到MAX了,额外的队列可以存储任务的线程池...

    背景:JDK的线程池的运作原理 : JDK的线程池的构造函数有7个参数,分别是corePoolSize.maximumPoolSize.keepAliveTime.unit.workQueue.thr ...

  5. java runnable main_Java 线程类问题写一个线程类MyThread,该线程实现了Runnable接口,写一个main方法, * 用for循...

    共回答了24个问题采纳率:91.7% interrupt(), stop(), suspend() 都不推荐再用,而是应该让 run 方法正常地退出,如果你打算让它循环,就在 run() 方法内用一个 ...

  6. 线程问题—一个线程怎么调出另外一个线程的结果。

    问题:建两线程,线程1进行计算1*1+~+99*99,线程2打印出线程1名字,每隔段时间读取一次线程1的计算结果? 1 public class Other{ 2 public static vola ...

  7. linux服务器开发二(系统编程)--线程相关

    线程概念 什么是线程 LWP:Light Weight Process,轻量级的进程,本质仍是进程(在Linux环境下). 进程:独立地址空间,拥有PCB. 线程:也有PCB,但没有独立的地址空间(共 ...

  8. Linux系统编程(九)线程同步

    Linux系统编程(九)线程同步 一.什么是线程同步? 二.互斥量 三.条件变量 pthread_cond_wait函数 pthread_cond_signal函数 生产者和消费者模型 一.什么是线程 ...

  9. linux线程时间片是多少_Linux 线程的实质

    线程与进程的比较 概述: 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小 ...

  10. linux c进程和线程脑图,进程和线程

    关于进程和线程,你需要理解下面这张脑图中的重点 进程 操作系统中最核心的概念就是 进程,进程是对正在运行中的程序的一个抽象.操作系统的其他所有内容都是围绕着进程展开的. 在多道程序处理的系统中,CPU ...

最新文章

  1. linux系统用户属组,关于 Linux系统用户、组和权限管理
  2. 【RecyclerView】 四、RecyclerView 布局 ( 网格局管理器 GridLayoutManager )
  3. Spring Boot配置@spring.profiles.active配置
  4. go websocket 关闭_Go实战--使用之gorilla/websocket
  5. 在页面加载完后执行jQuery代码
  6. 【LeetCode - 798】得分最高的最小轮调(转化法)
  7. eclipse java main方法传参数
  8. python处理带有‘\x‘的字符串,拆分,解码,重组
  9. python创建包含双引号的字符串代码_python 字符串组成MySql 命令时,字符串含有单引号或者双引号导致出错解决办法...
  10. BZOJ 1024: [SCOI2009]生日快乐
  11. Django 06模板语言的复用
  12. maxwell_电机气隙磁密与用matlab进行fft谐波分析,基于Maxwell的电机气隙磁场谐波分析程序...
  13. 大伽「趣」说AI:腾讯云在多个场景中的AI落地实践
  14. Chromium浏览器修改网页显示字体
  15. qt开发资料下载网址
  16. leetcode-017-297. 二叉树的序列化与反序列化
  17. abaqus6.10离线版user‘smanual
  18. GitHub下载 无法分配请求的地址_Hexo+Github--搭建个人博客(一)准备工作amp;amp;环境搭建
  19. 持安科技CEO何艺:零信任在实战攻防演练中的价值
  20. 站长号文库:什么是云存储?

热门文章

  1. Python:知道什么叫类吗,我这人实在不知道啥叫累。Python类的定义和使用。
  2. PLC自学速成秘诀,用好这个方法少走10年弯路
  3. Python列表实现斐波那契数列
  4. M1 Mac 疯狂读写SSD? 快来查看自己SSD读写数据
  5. 节后综合症的调整和对策
  6. 【sql:练习题2】查询平均成绩大于等于 60 分的同学的学生编号和学生姓名和平均成绩...
  7. 什么叫事件委托? JS事件代理原理分析
  8. Java之字符串(String)全解析
  9. C语言中血压检测问题,每次去医院量血压,看见医院的血压计都会有语音提示,这个是用什么来做的?...
  10. 90 后字节跳动员工内幕交易,被罚 50 万