c如何正常中断一个运行的线程
最近开发一些东西,线程数非常之多,当用户输入Ctrl+C的情形下,默认的信号处理会把程序退出,这时有可能会有很多线程的资源没有得到很好的释放,造成了内存泄露等等诸如此类的问题,本文就是围绕着这么一个使用场景讨论如何正确的终止正在运行的子线程。其实本文更确切的说是解决如何从待终止线程外部安全的终止正在运行的线程
首先我们来看一下,让当前正在运行的子线程停止的所有方法
1.任何一个线程调用exit
2.pthread_exit
3.pthread_kill
4.pthread_cancel
下面我们一一分析各种终止正在运行的程序的方法
任何一个线程调用exit
任何一个线程只要调用了exit都会导致进程结束,各种子线程当然也能很好的结束了,可是这种退出会有一个资源释放的问题.我们知道当一个进程终止时,内核对该进程所有尚未关闭的文件描述符调用close关闭,所以即使用户程序不调用close,在终止时内核也会自动关闭它打开的所有文件。没错,标准C++ IO流也会很好的在exit退出时得到flush并且释放资源,这些东西并不会造成资源的浪费(系统调用main函数入口类似于exit(main(argc,argv))).表面上似乎所有的问题都能随着进程的结束来得到很好的处理,其实并不然,我们程序从堆上分配的内存就不能得到很好的释放,如new ,delete后的存储空间,这些空间进程结束并不会帮你把这部分内存归还给内存.(本文初稿时,因基础不牢固,此处写错,事实上无论进程这样结束,系统都将会释放掉所有代码所申请的资源,无论是堆上的还是栈上的。(感谢ZKey的指导)。这种结束所有线程(包括主线程)的方式实际上在很多时候是非常可取的,但是对于针对关闭时进行一些别的逻辑的处理(指非资源释放逻辑)就不会很好,例如我想在程序被kill掉之前统计一下完成了多少的工作,这个统计类似于MapReduce,需要去每个线程获取,并且最后归并程一个统一的结果等等场景)
pthread_exit
此函数的使用场景是当前运行的线程运行pthread_exit得到退出,对于各个子线程能够清楚地知道自己在什么时候结束的情景下,非常好用,可是实际上往往很多时候一个线程不能知道知道在什么时候该结束,例如遭遇Ctrl+C时,kill进程时,当然如果排除所有的外界干扰的话,那就让每个线程干完自己的事情后,然后自觉地乖乖的调用pthread_exit就可以了,这并不是本文需要讨论的内容,本文的情景就是讨论如何处理特殊情况。
这里还有一种方法,既然子线程可以通过pthread_exit来正确退出,那么我们可以在遭遇Ctrl+C时,kill进程时处理signal信号,然后分别给在某一个线程可以访问的公共区域存上一个flag变量,线程内部每运行一段时间(很短)来检查一下flag,若发现需要终止自己时,自己调用pthread_exit,此法有一个弱点就是当子线程需要进行阻塞的操作时,可能无暇顾及检查flag,例如socket阻塞操作。如果你的子线程的任务基本没有非阻塞的函数,那么这么干也不失为一种很好的方案。
pthread_kill
不要被这个可怕的邪恶的名字所吓倒,其实pthread_kill并不像他的名字那样威力大,使用之后,你会感觉,他徒有虚名而已
pthread_kill的职责其实只是向指定的线程发送signal信号而已,并没有真正的kill掉一个线程,当然这里需要说明一下,有些信号的默认行为就是exit,那此时你使用pthread_kill发送信号给目标线程,目标线程会根据这个信号的默认行为进行操作,有可能是exit。当然我们同时也可以更改获取某个信号的行为,以此来达到我们终止子线程的目的。
1 #define _MULTI_THREADED
2 #include <pthread.h>
3 #include <stdio.h>
4 #include <signal.h>
5 #include "check.h"
6
7 #define NUMTHREADS 3
8 void sighand(int signo);
9
10 void *threadfunc(void *parm)
11 {
12 pthread_t self = pthread_self();
13 pthread_id_np_t tid;
14 int rc;
15
16 pthread_getunique_np(&self, &tid);
17 printf("Thread 0x%.8x %.8x entered\n", tid);
18 errno = 0;
19 rc = sleep(30);
20 if (rc != 0 && errno == EINTR) {
21 printf("Thread 0x%.8x %.8x got a signal delivered to it\n",
22 tid);
23 return NULL;
24 }
25 printf("Thread 0x%.8x %.8x did not get expected results! rc=%d, errno=%d\n",
26 tid, rc, errno);
27 return NULL;
28 }
29
30 int main(int argc, char **argv)
31 {
32 int rc;
33 int i;
34 struct sigaction actions;
35 pthread_t threads[NUMTHREADS];
36
37 printf("Enter Testcase - %s\n", argv[0]);
38
39 printf("Set up the alarm handler for the process\n");
40 memset(&actions, 0, sizeof(actions));
41 sigemptyset(&actions.sa_mask);
42 actions.sa_flags = 0;
43 actions.sa_handler = sighand;
44
45 rc = sigaction(SIGALRM,&actions,NULL);
46 checkResults("sigaction\n", rc);
47
48 for(i=0; i<NUMTHREADS; ++i) {
49 rc = pthread_create(&threads[i], NULL, threadfunc, NULL);
50 checkResults("pthread_create()\n", rc);
51 }
52
53 sleep(3);
54 for(i=0; i<NUMTHREADS; ++i) {
55 rc = pthread_kill(threads[i], SIGALRM);
56 checkResults("pthread_kill()\n", rc);
57 }
58
59 for(i=0; i<NUMTHREADS; ++i) {
60 rc = pthread_join(threads[i], NULL);
61 checkResults("pthread_join()\n", rc);
62 }
63 printf("Main completed\n");
64 return 0;
65 }
66
67 void sighand(int signo)
68 {
69 pthread_t self = pthread_self();
70 pthread_id_np_t tid;
71
72 pthread_getunique_np(&self, &tid);
73 printf("Thread 0x%.8x %.8x in signal handler\n",
74 tid);
75 return;
76 }
运行输出为:
1 Output:
2
3 Enter Testcase - QP0WTEST/TPKILL0
4 Set up the alarm handler for the process
5 Thread 0x00000000 0000000c entered
6 Thread 0x00000000 0000000d entered
7 Thread 0x00000000 0000000e entered
8 Thread 0x00000000 0000000c in signal handler
9 Thread 0x00000000 0000000c got a signal delivered to it
10 Thread 0x00000000 0000000d in signal handler
11 Thread 0x00000000 0000000d got a signal delivered to it
12 Thread 0x00000000 0000000e in signal handler
13 Thread 0x00000000 0000000e got a signal delivered to it
14 Main completed
我们可以通过截获的signal信号,来释放掉线程申请的资源,可是遗憾的是我们不能再signal处理里调用pthread_exit来终结掉线程,因为pthread_exit是中介当前线程,而signal被调用的方式可以理解为内核的回调,不是在同一个线程运行的,所以这里只能做处理释放资源的事情,线程内部只有判断有没有被中断(一般是EINTR)来断定是否要求自己结束,判定后可以调用pthread_exit退出。
此法对于一般的操作也是非常可行的,可是在有的情况下就不是一个比较好的方法了,比如我们有一些线程在处理网络IO事件,假设它是一种一个客户端对应一个服务器线程,阻塞从Socket中读消息的情况。我们一般在网络IO的库里面回家上对EINTR信号的处理,例如recv时发现返回值小于0,检查error后,会进行他对应的操作。有可能他会再recv一次,那就相当于我的线程根本就不回终止,因为网络IO的类有可能不知道在获取EINTR时要终止线程。也就是说这不是一个特别好的可移植方案,如果你线程里的操作使用了很多外来的不太熟悉的类,而且你并不是他对EINTR的处理手段是什么,这是你在使用这样的方法来终止就有可能出问题了。而且如果你不是特别熟悉这方面的话你会很苦恼,“为什么我的测试代码全是ok的,一加入你们部门开发的框架进来就不ok了,肯定是你们框架出问题了”。好了,为了不必要的麻烦,我最后没有使用这个方案。
pthread_cancel
这个方案是我最终采用的方案,我认为是解决这个问题,通用的最好的解决方案,虽然前面其他方案的有些问题他可能也不好解决,但是相比较而言,还是相当不错的
pthread_cancel可以单独使用,因为在很多系统函数里面本身就有很多的断点,当调用这些系统函数时就会命中其内部的断点来结束线程,如下面的代码中,即便注释掉我们自己设置的断点pthread_testcancel()程序还是一样的会被成功的cancel掉,因为printf函数内部有取消点(如果大家想了解更多的函数的取消点情况,可以阅读《Unix高级环境编程》的线程部分)
1 #include <pthread.h>
2 #include <stdio.h>
3 #include<stdlib.h>
4 #include <unistd.h>
5 void *threadfunc(void *parm)
6 {
7 printf("Entered secondary thread\n");
8 while (1) {
9 printf("Secondary thread is looping\n");
10 pthread_testcancel();
11 sleep(1);
12 }
13 return NULL;
14 }
15
16 int main(int argc, char **argv)
17 {
18 pthread_t thread;
19 int rc=0;
20
21 printf("Entering testcase\n");
22
23 /* Create a thread using default attributes */
24 printf("Create thread using the NULL attributes\n");
25 rc = pthread_create(&thread, NULL, threadfunc, NULL);
26 checkResults("pthread_create(NULL)\n", rc);
27
28 /* sleep() is not a very robust way to wait for the thread */
29 sleep(1);
30
31 printf("Cancel the thread\n");
32 rc = pthread_cancel(thread);
33 checkResults("pthread_cancel()\n", rc);
34
35 /* sleep() is not a very robust way to wait for the thread */
36 sleep(10);
37 printf("Main completed\n");
38 return 0;
39 }
输出:
Entering testcase
Create thread using the NULL attributes
Entered secondary thread
Secondary thread is looping
Cancel the thread
Main completed
POSIX保证了绝大部分的系统调用函数内部有取消点,我们看到很多在cancel调用的情景下,recv和send函数最后都会设置pthread_testcancel()取消点,其实这不是那么有必要的,那么究竟什么时候该pthread_testcancel()出场呢?《Unix高级环境编程》也说了,当遇到大量的基础计算时(如科学计算),需要自己来设置取消点。
ok,得益于pthread_cancel,我们很轻松的把线程可以cancel掉,可是我们的资源呢?何时释放...
下面来看两个pthread函数
1.void pthread_cleanup_push(void (*routine)(void *), void *arg);
2.void pthread_cleanup_pop(int execute);
这两个函数能够保证在 1函数调用之后,2函数调用之前的任何形式的线程结束调用向pthread_cleanup_push注册的回调函数
另外我们还可通过下面这个函数来设置一些状态
int pthread_setcanceltype(int type, int *oldtype);
Cancelability | Cancelability State | Cancelability Type |
---|---|---|
disabled | PTHREAD_CANCEL_DISABLE | PTHREAD_CANCEL_DEFERRED |
disabled | PTHREAD_CANCEL_DISABLE | PTHREAD_CANCEL_ASYNCHRONOUS |
deferred | PTHREAD_CANCEL_ENABLE | PTHREAD_CANCEL_DEFERRED |
asynchronous | PTHREAD_CANCEL_ENABLE | PTHREAD_CANCEL_ASYNCHRONOUS |
当我们设置type为PTHREAD_CANCEL_ASYNCHRONOUS时,线程并不会等待命中取消点才结束,而是立马结束
好了,下面贴代码:
1 #include <pthread.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <unistd.h>
5 #include <errno.h>
6 int footprint=0;
7 char *storage;
8 void freerc(void *s)
9 {
10 free(s);
11 puts("the free called");
12 }
13
14 static void checkResults(char *string, int rc) {
15 if (rc) {
16 printf("Error on : %s, rc=%d",
17 string, rc);
18 exit(EXIT_FAILURE);
19 }
20 return;
21 }
22
23 void *thread(void *arg) {
24 int rc=0, oldState=0;
25 rc = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); //close the cancel switch
26 checkResults("pthread_setcancelstate()\n", rc);
27 if ((storage = (char*) malloc(80)) == NULL) {
28 perror("malloc() failed");
29 exit(6);
30 }
31 rc = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,&oldState); //open the cancel switch
32 checkResults("pthread_setcancelstate(2)\n", rc);
33 /* Plan to release storage even if thread doesn't exit normally */
34
35 pthread_cleanup_push(freerc, storage); /*the free is method here you can use your own method here*/
36
37 puts("thread has obtained storage and is waiting to be cancelled");
38 footprint++;
39 while (1)
40 {
41 pthread_testcancel(); //make a break point here
42 //pthread_exit(NULL); //test exit to exam whether the freerc method called
43 sleep(1);
44 }
45
46 pthread_cleanup_pop(1);
47 }
48
49 main() {
50 pthread_t thid;
51 void *status=NULL;
52
53 if (pthread_create(&thid, NULL, thread, NULL) != 0) {
54 perror("pthread_create() error");
55 exit(1);
56 }
57
58 while (footprint == 0)
59 sleep(1);
60
61 puts("IPT is cancelling thread");
62
63 if (pthread_cancel(thid) != 0) {
64 perror("pthread_cancel() error");
65 sleep(2);
66 exit(3);
67 }
68
69 if (pthread_join(thid, &status) != 0) {
70 if(status != PTHREAD_CANCELED){
71 perror("pthread_join() error");
72 exit(4);
73 }
74 }
75 if(status == PTHREAD_CANCELED)
76 puts("PTHREAD_CANCELED");
77
78 puts("main exit");
79 }
相关文章:

Vertica 分区表设计(续)
在上篇Vertica 分区表设计中,已经提过了Vertica的分区表创建和分区删除,但举例上并不系统, 本篇文章将系统的对分区表设计及后续的删除分区进行讲解。 概述:Vertica分区表(天和月)创建以及删除分区 1.分区表…

【ACM】杭电OJ 1181
http://acm.hdu.edu.cn/showproblem.php?pid1181 DFS搜索(递归函数) #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cstdlib> using namespace std; char s[1000]; int k…

最热开源无服务器函数:五大Fission架构参考
“无服务器”现在是极具诱惑的技术趋势,没有什么比管理服务器更让人痛苦。亚马逊、微软和谷歌都在云中提供无服务器专有接口。相较于这些云供应商的商业化产品,开源无服务器架构可免于被云厂商锁定,但要以牺牲云便利性和易用性为代价。近一年…
高德API+Python解决租房问题
项目简介:编写Python脚本爬取某租房网站的房源信息,利用高德的 js API 在地图上标出房源地点,划出距离工作地点1小时内可到达的范围,附上公交路径规划功能查看不同路径的用时。 本教程由ekCit发布在实验楼,完整教程及在…

SIMD向量化运算
随着机器学习等人工智能技术的飞速发展,矩阵乘法的应用越来越多,intel芯片先后提供了不同系列的向量指令,包括mmx、sse、avx等,支持simd操作。后来为了更好地支持矩阵乘法,又增加了fma(Fused Multiply-Add&…
【数据结构】二叉树及其相关操作
二叉树的定义 二叉树是一个由结点构成的有限集合,这个集合或者为空,或者由一个根节点及两棵互不相交的分别称作这个根节点的左子树和右子树的二叉树组成。 二叉树并非一般的树形结构的特殊形式,它们是两种不同的数据结构。 二叉树与一般树…
函数节流与函数防抖
什么是函数节流与函数防抖 举个栗子,我们知道目前的一种说法是当 1 秒内连续播放 24 张以上的图片时,在人眼的视觉中就会形成一个连贯的动画,所以在电影的播放(以前是,现在不知道)中基本是以每秒 24 张的速…

makefile 中 =, :=, ?=, +=的区别
在Makefile中我们经常看到 : ? 这几个赋值运算符,那么他们有什么区别呢?我们来做个简单的实验 新建一个Makefile,内容为: ifdef DEFINE_VRE VRE “Hello World!” else endif ifeq ($(OPT),define) VRE ? “Hello W…

ubuntu 编译源码包 dsc diff.gz orig.tar.gz
2019独角兽企业重金招聘Python工程师标准>>> 1) 在获取源码包之前,确保在软件源配置文件/etc/apt/sources.list中添加了deb-src项以tree实用程序(以树型结构获取目录树)为例,介绍Ubuntu中如何管理源码包&am…

【ACM】杭电OJ 2552
本来还查了atan 和 atan2 的用法,结果总是WA 看了解析之后才知道原来是要公式推导,最后得出所求的式子是一个等式,结果为1。 所以,以后出类似与数学公式的题,可能是要手算推到,在输出特定的结果。&#x…

蚂蚁金服天街:OceanBase 在大促 5 年来的技术演进
为了与金融从业者、科技从业者共同探讨金融 业务的深层次问题,蚂蚁金服联手 TGO 鲲鹏会,在 12 月 8 日举办了「走进蚂蚁金服:双十一背后的蚂蚁金服技术支持」活动。蚂蚁金服高级技术专家天街为大家分享了《蚂蚁双 11 大促 OceanBase 核心技术…

OTA升级flash分区
什么是在线OTA升级 - OTA是Over-the-Air的简写,空中下载技术的意思。 - OTA在线升级在日常消费电子产品中很常见,比如手机,机顶盒等,通过网络,下载升级数据包,更新操作系统等底层固件进行…

MD5与Base64的思考
MD5加密是对任意长的数据使用MD5哈稀算法散列为4个32位组,若格式化为ASCII字符则为16字符,若格式化16进制表示,则为32字符. (MD5的具体算法请参阅相关书籍和资料)MD5广泛用于数据校验和完整性检验.且不可逆.理论上为抗碰撞的在2004年8月17日,MD5遭遇重创,山东大学的王小云做了…
【ACM】杭电OJ 1076
数组要开的大一些,一开始数组只开到100005,就显示了错误的数据 AC代码: #include <iostream> #include <cstring> using namespace std; const int maxn 10000005; int a[maxn]; int main () {int i;memset(a,0,sizeof(a));fo…

IDEA ctrl+alt+L 格式化快捷键无效时解决
这几天发现自己Intellij IDEA ctrlaltL格式化代码无效 设置里面按照快捷键搜索 按了 ctrlaltL 也没反应 但是我设置的确实是默认的 ctrlaltL 最后终于找到了问题所在 原来是开网易云音乐的锅 网易云会有一个全局的快捷键ctrlaltL跟idea冲突 去网易云关了就好了 转载于:https:/…

gpio pin和pad的区别
PIN指芯片封装好后的管脚,即用户看到的管脚; PAD是硅片的管脚,是封装在芯片内部的,用户看不到。 PAD到PIN之间还有一段导线连接的。
【ACM】杭电OJ 1013
WA代码 输入很大的数的时候会输出“-1”,所以考虑用字符数组来储存输入的数据。 #include <iostream> #include <cstring> #include <cstdio> using namespace std; long long sum; long long fun (int n) {sum0;if(n<9) return n;while(n){s…

\\s+ split替换
出自: http://www.tuicool.com/articles/vy2ymm 详解 "\\s" 正则表达式中\s匹配任何空白字符,包括空格、制表符、换页符等等, 等价于[ \f\n\r\t\v] \f -> 匹配一个换页\n -> 匹配一个换行符\r -> 匹配一个回车符\t -> 匹配一个制表…

ubuntu18.04下双机驱动调试
环境搭建:https://blog.51cto.com/haidragon/2337256这里要先说下如果要下内核断点要先在编译前去掉写保护,但是下自己写的驱动可以不要。第二个最好编译完后压缩vm系统文件然后复制一份,这样就调试机与被调试机环境一模一样,同样…

如何独立开发一个网络请求框架
(原创出处为本博客:http://www.cnblogs.com/linguanh/) 目录: 前言 准备工作 开发模式 开发原则 线程 高并发 TCP/UDP 本类介绍 开发选择 功能列表 优点 拓展 完整代码 用法例子 前言: 已开源到GitHub,希望…
【ACM】杭电OJ 1284(待更)
#include<iostream> using namespace std; int main(){int n;while(cin>>n){int ans0; for(int i0;i<n/3;i){ //对3的个数进行枚举 int temp(n-3*i); //除了这i个3之外剩余的钱数 //temp/2,剩余部分换成2的总种类数,anstemp/21; //这…

c语言头文件中定义inline static相关函数的优劣
头文件中常见static inline函数,于是思考有可能遇到的问题,如头文件经常会被包含会不会产生很多副本?网上说法不一。于是自己验证。经过arm-none-eabi-gcc下测试后得出结论。 inline 关键字实际上仅是建议内联并不强制内联,gcc中O…

c语言inline详解
本文介绍了GCC和C99标准中inline使用上的不同之处。inline属性在使用的时候,要注意以下两点:inline关键字在GCC参考文档中仅有对其使用在函数定义(Definition)上的描述,而没有提到其是否能用于函数声明(Dec…

【ACM】杭电OJ 2090
题目中给出的四舍五入的条件可以忽略不计了,因为提交的程序没有考虑四舍五入,照样AC了 printf("%.1lf\n",sum); AC代码: 写的有点复杂了,其实不用定义结构体也可以。 #include<iostream> #include <cstdi…

属性配置文件详解(2)(十七)
过命令行设置属性值 相信使用过一段时间Spring Boot的用户,一定知道这条命令:java -jar xxx.jar --server.port8888,通过使用–server.port属性来设置xxx.jar应用的端口为8888。 在命令行运行时,连续的两个减号--就是对applicatio…

git track远程分支
在本地初始化仓库,提交代码时会出现,上游为空,当前分支为选择,等错误提示。其实就是本地仓库分支和远程仓库分支并未进行关联,即本地分支未追踪到远程分支。 1.本地和远程的状态 本地: 本地所有的文…

HTMLDOM中三种元素节点、属性节点、文本节点的测试案例
HTML dom中常用的三种节点分别是元素节点、属性节点、文本节点。 具体指的内容可参考下图: 以下为测试用例: <!DOCTYPE html> <html><head><title>元素节点、属性节点、文本节点的测试</title><meta name"Author" conte…

【ACM】DFS 全排列 回溯
深入体会一下DFS,回溯 在一些OJ上endl和“\n”还是有区别的!!! 题目链接:http://codevs.cn/problem/1294/ 方法一: #include <iostream> #include <cstdio> #include <cstring> usin…

(轉貼) 友達光電第五屆【A+種子暑期實習計畫】開始辦理報名 (News)
友達光電第五屆【A種子暑期實習計畫】開始辦理報名 友達光電以絕佳的團隊執行力,帶領台灣光電產業進入世界級的領域! 還在就學的你/妳,想成為世界級光電產業的A種子嗎? 把握最後的暑假加入友達的A種子實習團隊吧!! 【2008 A種子募集計畫】 實習期間&am…

binutils工具集用法
addr2line用于得到程序指令地址所对应的函数,以及函数所在的源文件名和行号。 在不少嵌入式开发环境中,编译器的名称往往不是gcc,而是想arm-rtems-gcc这样的,对于这种命名形式的编译器,读者通常可以找到arm-rtems-add…