当前位置: 首页 > 编程日记 > 正文

bug诞生记——信号(signal)处理导致死锁

这个bug源于项目中一个诡异的现象:代码层面没有明显的锁的问题,但是执行时发生了死锁一样的表现。我把业务逻辑简化为:父进程一直维持一个子进程。(转载请指明出于breaksoftware的csdn博客)

首先我们定义一个结构体ProcessGuard,它持有子进程的ID以及保护它的的锁。这样我们在多线程中,可以安全的操作这个结构体。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <pthread.h>struct ProcessGuard {pthread_mutex_t pids_mutex;pid_t pid;
};

主进程的主线程启动一个线程,用于不停监视ProcessGuard的pid是否为0(即子进程不存在)。如果不存在就创建子进程,并把进程ID记录到pid中;

void chile_process() {while (1) {printf("This is the child process. My PID is %d.My thread_id is %lu.\n", getpid(), pthread_self());sleep(1);}
}void create_process_routine() {printf("This is the child thread of parent process. My PID is %d.My thread_id is %lu.\n", getpid(), pthread_self());while (1) {int child = 0;if (child == 0) {pthread_mutex_lock(&g_guard->pids_mutex);}if (g_guard->pid != 0) {continue;    }pid_t pid = fork();sleep(1);printf("Create child process %d.\n", pid);if (pid < 0) {perror("fork failed");}else if (pid == 0) {chile_process();child = 1;break;}else {// parent processg_guard->pid = pid;printf("dispatch task to process. pid is %d.\n", pid);}if (child == 0) {pthread_mutex_unlock(&g_guard->pids_mutex);  }else {break;}}
}

我们在父进程的主线程中注册一个signal监听。如果子进程被杀掉,则将ProcessGuard中pid设置为0,这样父进程的监控线程将重新启动一个进程。

void sighandler(int signum) {printf("This is the parent process.Catch signal %d.My PID is %d.My thread_id is %lu.\n", signum, getpid(), pthread_self());pthread_mutex_lock(&g_guard->pids_mutex);g_guard->pid = 0;pthread_mutex_unlock(&g_guard->pids_mutex); 
}

最后看下父进程,它初始化一些结构后,注册了signal处理事件并启动了创建子进程的线程。

int main(void) {pthread_t creat_process_tid;g_guard = malloc(sizeof(struct ProcessGuard));pthread_mutex_t pids_mutex;if (pthread_mutex_init(&g_guard->pids_mutex, NULL) != 0) {perror("init pids_mutex error.");exit(1);}g_guard->pid = 0;printf("This is the Main thread of parent process.PID is %d.My thread_id is %lu.\n", getpid(), pthread_self());signal(SIGCHLD, sighandler);pthread_create(&creat_process_tid, NULL, (void*)create_process_routine, NULL);while(1)  {printf("Get task from network.\n");sleep(1);}pthread_mutex_destroy(&g_guard->pids_mutex);return 0;
}

上述代码,我们看到锁只在线程函数create_process_routine和signal处理函数sighandler中被使用了。它们两个在代码层面没有任何调用关系,所以不应该出现死锁!但是实际并非如此。

我们运行程序,并且杀死子进程,会发现主进程并没有重新启动一个新的子进程。

$ ./test      
This is the Main thread of parent process.PID is 17641.My thread_id is 140014057678656.
Get task from network.
This is the child thread of parent process. My PID is 17641.My thread_id is 140014049122048.
Create child process 17643.
dispatch task to process. pid is 17643.
Create child process 0.
This is the child process. My PID is 17643.My thread_id is 140014049122048.
Get task from network.
This is the child process. My PID is 17643.My thread_id is 140014049122048.
This is the child process. My PID is 17643.My thread_id is 140014049122048.
Get task from network.
This is the child process. My PID is 17643.My thread_id is 140014049122048.
This is the child process. My PID is 17643.My thread_id is 140014049122048.
This is the child process. My PID is 17643.My thread_id is 140014049122048.
Get task from network.
This is the child process. My PID is 17643.My thread_id is 140014049122048.
Get task from network.
This is the child process. My PID is 17643.My thread_id is 140014049122048.
This is the child process. My PID is 17643.My thread_id is 140014049122048.
Get task from network.
This is the child process. My PID is 17643.My thread_id is 140014049122048.
This is the child process. My PID is 17643.My thread_id is 140014049122048.
This is the child process. My PID is 17643.My thread_id is 140014049122048.
Get task from network.
This is the child process. My PID is 17643.My thread_id is 140014049122048.
This is the child process. My PID is 17643.My thread_id is 140014049122048.
Get task from network.
This is the parent process.Catch signal 17.My PID is 17641.My thread_id is 140014049122048.
Get task from network.
Get task from network.
Get task from network.
Get task from network.
Get task from network.

这个和我们代码设计不符合,而且不太符合逻辑。于是我们使用gdb attach主进程。

Attaching to process 17641
[New LWP 17642]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
0x00007f578fb7a9d0 in __GI___nanosleep (requested_time=requested_time@entry=0x7fffd2b41190, remaining=remaining@entry=0x7fffd2b41190) at ../sysdeps/unix/sysv/linux/nanosleep.c:28
28      ../sysdeps/unix/sysv/linux/nanosleep.c: No such file or directory.
(gdb) info threadsId   Target Id         Frame 
* 1    Thread 0x7f57902be740 (LWP 17641) "test" 0x00007f578fb7a9d0 in __GI___nanosleep (requested_time=requested_time@entry=0x7fffd2b41190, remaining=remaining@entry=0x7fffd2b41190)at ../sysdeps/unix/sysv/linux/nanosleep.c:282    Thread 0x7f578fa95700 (LWP 17642) "test" __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
(gdb) t 2
[Switching to thread 2 (Thread 0x7f578fa95700 (LWP 17642))]
#0  __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
135     ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S: No such file or directory.
(gdb) bt
#0  __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
#1  0x00007f578fe91023 in __GI___pthread_mutex_lock (mutex=0x55c51383e260) at ../nptl/pthread_mutex_lock.c:78
#2  0x000055c512c29a9d in sighandler ()
#3  <signal handler called>
#4  __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:133
#5  0x00007f578fe91023 in __GI___pthread_mutex_lock (mutex=0x55c51383e260) at ../nptl/pthread_mutex_lock.c:78
#6  0x000055c512c29b42 in create_process_routine ()
#7  0x00007f578fe8e6db in start_thread (arg=0x7f578fa95700) at pthread_create.c:463
#8  0x00007f578fbb788f in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

我们查看线程2的调用栈,发现栈帧5和栈帧1锁住了相同的mutex(0x55c51383e260)。而我们线程代码中锁是加/解成对,那么第二个锁是哪儿来的呢?

我们看到栈帧1的锁是源于栈帧2对应的函数sighandler,即下面代码

void sighandler(int signum) {printf("This is the parent process.Catch signal %d.My PID is %d.My thread_id is %lu.\n", signum, getpid(), pthread_self());pthread_mutex_lock(&g_guard->pids_mutex);g_guard->pid = 0;pthread_mutex_unlock(&g_guard->pids_mutex); 
}

于是,问题来了。我们在线程函数create_process_routine中从来没有调用sighandler,那这个调用是哪儿来的?

在linux文档http://man7.org/linux/man-pages/man7/signal.7.html中,我们发现了有关signal的这段话

A process-directed signal may be delivered to any
one of the threads that does not currently have the signal blocked.
If more than one of the threads has the signal unblocked, then the
kernel chooses an arbitrary thread to which to deliver the signal.

这句话是说process-directed signal会被投递到当前没有被标记不接受该signal的任意一个线程中。 具体是哪个,是由系统内核决定的。这就意味着我们的sighandler可能在主线程中执行,也可能在子线程中执行。于是发生了我们上面的死锁现象。

那么如何解决?官方的方法是使用sigprocmask让一些存在潜在死锁关系的线程不接收这些信号。但是这个方案在复杂的系统中是存在缺陷的。因为我们的工程往往使用各种开源库或者第三方库,我们无法控制它们启动线程的问题。所以,我的建议是:在signal处理函数中,尽量使用无锁结构。通过中间数据的设计,将复杂的业务代码和signal处理函数隔离。

相关文章:

Linux下SVN服务器支持Apache的http和svnserve独立服务器

2019独角兽企业重金招聘Python工程师标准>>> 说明 服务器操作系统&#xff1a;CentOS 6.6 关闭防火墙&#xff0c;SElinux 实现 1、在服务器上安装配置SVN服务&#xff1b; 2、SVN服务支持svnserve独立服务模式访问&#xff1b; 3、SVN服务支持Apache的http模式访问…

AWS攻略——使用CodeCommit托管代码

除了我们熟悉的github&#xff0c;各大云厂商也有自己的代码托管服务。本文讲解如何在Amazon的CodeCommit中托管代码。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 根账户登录 AWS有两种账户登录界面。 IAM账户登录界面 根账户登录界面我们先使用根…

使用alterMIME实现添加message footer功能

1. 安装alterMIME tar zxvf altermime-0.3.8.tar.gz cd altermin3-0.3.8 make make install altermine将被编译安装到/usr/local/bin/2. 使用必备条件&#xff1a;一个运行且配置正常的邮件服务器3. 配置AlterMIME3.1 为altermine创建一个系统帐号&#xff0c;如下&#x…

Facebook最新研究:无需额外训练AI,即可加速NLP任务

作者 | KYLE WIGGERS译者 | Kolen出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;自然语言模型通常要解决两个难题&#xff1a;将句子前缀映射到固定大小的表示形式&#xff0c;并使用这些表示形式来预测文本中的下一个单词。在最近的一篇论文&#xff08;https://ar…

PgSQL · 特性分析 · full page write 机制

PG默认每个page的大小为8K&#xff0c;PG数据页写入是以page为单位&#xff0c;但是在断电等情况下&#xff0c;操作系统往往不能保证单个page原子地写入磁盘&#xff0c;这样就极有可能导致部分数据块只写到4K(操作系统是一般以4K为单位)&#xff0c;这些“部分写”的页面包含…

局域网DVD yum源的制作

今天在网上溜达,看到这篇文章不错,于是就转载过来,感谢原作者的辛苦劳动.源地址:http://blog.chinaunix.net/u3/94782/showart_1953260.html一&#xff1a;两台计算机做实验<?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office" />1&…

AWS攻略——使用S3托管静态网页

在AWS上有很多部署静态网页的方式&#xff0c;比如使用EC2或者Lightsail。但是不管使用上述哪种方案&#xff0c;都需要预先部署如Nignx或者Apache等Http服务。这对纯前端同学来说可能有点复杂&#xff0c;而AWS提供了更简单的部署方式——只需要提供静态网页文件的“S3网页托管…

2020年涨薪26-30%,能实现吗?18%数据科学家是这么期待的

作者丨Big Cloud编译 | 武明利&#xff0c;责编丨Carol出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;本报告将深入探讨亚太地区各个背景、不同年龄和不同地点的专业人员对2019/2020年的见解。今年贡献最大的地区来自新加坡和澳大利亚。因为这些是我们最大的数据点&…

AWS攻略——使用CodeBuild进行自动化构建和部署静态网页

首先声明下&#xff0c;使用“CodeBuild”部署并不是“正统”的方案&#xff0c;因为AWS提供了“CodeDeploy”。如果不希望引入太多基础设施&#xff0c;可以考虑直接使用CodeBuild进行部署。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 创建构建项目 kro…

我们需要什么样的数据架构?

作者 | Stephanie shen编译 | 火火酱&#xff0c;责编丨Carol出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;在大数据和数据科学的新时代&#xff0c;对企业而言&#xff0c;一定要有与业务流程保持一致的中心化数据架构&#xff0c;该架构能随业务增长而扩展&#…

Windows Server 2008 R2 之二十九故障转移群集(一)(

关于Windows Server 2008故障转移群集见http://technet.microsoft.com/zh-cn/library/cc732488(WS.10).aspx实验环境&#xff1a;两台已安装好Windows Server 2008 R2的计算机R2DC01、R2DC02,均为DC、DNS&#xff0c;域名为HBYCRSJ.COM,均有两块网卡。分别为心跳网络和本地连接…

基于多核DSP处理器DM8168的视频处理方法

摘要&#xff1a;随着1080P高清视频以及4K超高清晰视频的普及和应用&#xff0c;基于传统单核DSP处理器的视频信息处理已有些力不从心。为此TI公司推出了一款专门用于高清视频处理的多核DSP处理器&#xff0c;它拥有4个不同类型的处理器&#xff0c;使得视频处理达到了一个更高…

AWS攻略——使用CodeBuild进行自动化构建和部署Lambda(Python)

Aws Lambda是Amazon推出的“无服务架构”服务。我们只需要简单的上传代码&#xff0c;做些简单的配置&#xff0c;便可以使用。而且它是按运行时间收费&#xff0c;这对于低频访问的服务来说很划算。具体的介绍可以常见aws lambda的官网。&#xff08;转载请指明出于breaksoftw…

vmware 添加 磁盘 空间

VMware安装linux的时候默认分配的空间是4GB&#xff0c;可能会不够&#xff0c;这个时候可以通过增加一块虚拟硬盘&#xff0c;将/usr或其他内容拷贝过去解决这个问题&#xff1a;创建虚拟硬盘1、关闭VM中正在运行的虚拟系统&#xff1b;2、在虚拟系统名称上点右键&#xff0d;…

Python爬取考研数据:所有985高校、六成211高校均可调剂

又到了一年一度的考研出分时间啦&#xff0c;近期有不少朋友让笔者帮他们分析如何提前做好调剂。复试与调剂总是密不可分。今天&#xff0c;给大家分享一些调剂的重要知识点&#xff0c;希望你在调剂的时候&#xff0c;能明白调剂的趋势与规则。也许&#xff0c;大家对于调剂的…

iOS审核秘籍】提审资源检查大法

iOS审核秘籍】提审资源检查大法 2015/11/27阅读&#xff08;752&#xff09;评论&#xff08;1&#xff09;收藏&#xff08;6&#xff09;加入人人都是产品经理【起点学院】产品经理实战训练营&#xff0c;BAT产品总监手把手带你学产品点此查看详情&#xff01; 本篇主要是提审…

谈一次单元测试驱动代码重构

目前团队并没有QA岗&#xff0c;而且在很长一段时间内&#xff0c;可能也不会设立QA岗&#xff0c;所以我们需要RD保证代码的质量。而鉴于人类天生的“惰性”&#xff0c;很多时候质量完全依赖于作者的能力以及职业素质。于是我在团队内推动单元测试&#xff0c;并要求提升测试…

新机会在广州拓波

公司简介广州拓波软件科技有限公司的前身为 Turbomail工作室&#xff0c;由广州华工信息软件&#xff08;集团&#xff09;有限公司于2002 年成立&#xff0c;是一家专业研发电子邮件系统、企业即时通信和企业短信的开发组织&#xff0c;2005年TurboMail工作室正式发布1.0.2版本…

关于正则表达式,这篇都讲清楚了

作者 | 猪哥来源 | 裸睡的猪&#xff08;ID:rgznai100&#xff09;目前越来越多的网站、编辑器、编程语言都已支持一种叫“正则表达式”的字符串查找“公式”&#xff0c;有过编程经验的同学都应该了解正则表达式&#xff08;Regular Expression 简写regex&#xff09;是什么东…

MJExtension简介

MJExtension简介 前言&#xff1a;关于MJExtension更多的使用&#xff0c;可以到github网站上根据详述学习。 字典转模型比较流行的第三方框架 Mantle所有模型都必须继承自MTModel JSONModel所有模型都必须继承自JSONModel MJExtension不需要强制继承任何其他类 框架需要考虑的…

Discuz!常用函数解析(续)

/*** 产生随机码* param $length - 要多长* param $numberic - 数字还是字符串* return 返回字符串*/function random($length, $numeric 0) {PHP_VERSION < 4.2.0 && mt_srand((double)microtime() * 1000000);if($numeric) {$hash sprintf(%0.$length.d, mt_ran…

基于新型忆阻器的存内计算原理、研究和挑战

作者 | 林钰登、高滨、王小虎、钱鹤、吴华强来源 | 《微纳电子与智能制造》期刊引言过去半个世纪以来 &#xff0c;芯片计算性能的提高主要依赖于场效应晶体管尺寸的缩小。随着特征尺寸的减小 &#xff0c;器件的制备成本和制造工艺难度不断增加 &#xff0c;芯 片性能的提升愈…

3、JPA一些常用的注解

常用注解有下面这些&#xff1a; ①&#xff1a;Entity、Table、Id、GeneratedValue、Column、Basic ②&#xff1a;Transient 用于忽略某个属性&#xff0c;而不对该属性进行持久化操作 ③&#xff1a;Temporal 一、第①组注解 Entity 标注用于实体类声明语句之前&#xff0c…

实战域树部署,Active Directory系列之十九

实战子域部署<?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office" />域树是Active Directory针对NT4的传统域模型所进行的重要改进。在NT4时代的域模型中&#xff0c;每个域都要使用没有层次结构的NETBIOS名称&#xff0c;而且域和域之…

黑科技抗疫,Python开发者大集结!

2020年初&#xff0c;突如其来的新型冠状病毒肺炎打乱了所有人的节奏&#xff0c;但社会各界迅速团结起来&#xff0c;为抗击疫情贡献出自己的力量。除了捐款捐物外&#xff0c;很多科技公司运用5G、大数据、AI、云计算等新互联网技术&#xff0c;以科技的手段助力抗疫&#xf…

Inplayable技术分享

Inplayable技术分享运维设计模式Web安全工具语言python运维 《aws lambda 通过codebuild上线踩坑指南之 lambda 进程被占用 status error 255》《google pay 配置sub/pub回调》《AWS攻略——使用CodeCommit托管代码》《AWS攻略——使用S3托管静态网页》《AWS攻略——使用CodeB…

将数组A中的内容和数组B中的内容进行交换(数组一样大)

#include <stdio.h>int main() {int arr1[10]{1,2,3,4,5,11,14,16,17,12};int arr2[10]{0,6,7,8,9,15,21,18,19,13};int arr3[10];int i0;for(i0;i<sizeof(arr1)/sizeof(arr1[0]);i){arr3[i]arr1[i];arr1[i]arr2[i];arr2[i]arr3[i];//不定义第三个变量的两种种方法&am…

***必备工具

***必备工具一、扫描工具 X-scan 3.1 焦点出的扫描器&#xff0c;国内最优秀的安全扫描软件之一!非常专业的一个扫描器! X-way 2.5 这也上一个非常不错的扫描器哦!功能非常多!使用也不难,***必备工具! SuperScan 3.0 强大的TCP 端口扫描器、Ping 和域名解析器! Namp 3.5 这个就…

通过评估假设行为来学习人类目标

来源| deepmind编译| 武明利&#xff0c;责编| Carol出品 | AI科技大本营&#xff08;ID&#xff1a;rgznai100&#xff09;当我们在现实世界中训练强化学习&#xff08;RL&#xff09;代理时&#xff0c;我们不会希望它们探索不安全的状态&#xff0c;例如将一个移动机器人开进…

ReactiveCocoa入门-part2

ReactiveCocoa是一个框架&#xff0c;它能让你在iOS应用中使用函数响应式编程&#xff08;FRP&#xff09;技术。在本系列教程的第一部分中&#xff0c;你学到了如何将标准的动作与事件处理逻辑替换为发送事件流的信号。你还学到了如何转换、分割和聚合这些信号。 在本系列教程…