【Linux应用编程】一个异步信号处理引起死锁问题的思考

2023-05-16

文章目录

  • 1 前言
  • 2 为什么会产生死锁
    • 2.1 死锁
    • 2.2 分析
    • 2.3 结论
  • 3 避免死锁
  • 4 举一反三
  • 5 死锁例子代码
  • 6 参考文章


1 前言

  最近在维护别人的代码时,遇到一个线程死锁问题,一番折腾,最终定位到的是“信号异步处理引发死锁问题”。“信号异步处理死锁问题”是一个老生常谈的问题了,虽然问题简单,但定位起来仍需花费点时间;如果代码量大、复现概率低,还需花费更多的人力。因此,有必要回顾下这个问题,避免踩坑,包括新手和老手都可能踩坑。


  死锁问题原型伪代码:

void signal_handle(int signo) 
{
	pthread_mutex_lock(&mutex);
	/* todo */
	pthread_mutex_unlock(&mutex);
	return; 
}

int main(int argc, char *argv)
{
	signal(SIGALRM, signal_handle);
	pthread_mutex_init(&mutex, NULL);
	printf("Main thread id:0x%lx\n", syscall(SYS_gettid));
	for(;;)
	{
		pthread_mutex_lock(&mutex);
		/* todo */
		pthread_mutex_unlock(&mutex);
		usleep(10);
	}
	return 0; 
}

2 为什么会产生死锁

2.1 死锁

  死锁(DeadLock) 是指两个或者两个以上的进程(线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程(线程)称为死锁进程(线程)。

  根据死锁的定义,死锁产生的条件是:

  • 两个进程(线程)以上
  • 有资源竞争

2.2 分析

  对于信号回调函数,与主线程是同一线程(线程ID相同),不满足两个进程(线程)的条件,为什么会发生死锁呢?下面我们先通过一段代码验证信号回调函数与主线程是否为同一个线程。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/syscall.h>

void signal_handle(int signo) 
{
	printf("Signal thread id:0x%lx\n", syscall(SYS_gettid));
	return; 
}

int main(int argc, char *argv)
{
	signal(SIGALRM, signal_handle);
	printf("Main thread id:0x%lx\n", syscall(SYS_gettid));
	alarm(1);
	sleep(1);
	return 0; 
}

  编译执行:

acuity@ubuntu:/mnt/hgfs/LSW/temp$ gcc signal.c -o signal 
acuity@ubuntu:/mnt/hgfs/LSW/temp$ ./signal
Main thread id:0x1509
Signal thread id:0x1509

  通过测试代码可以知道,信号回调函数与主线程的ID一致,说明两者实质是同一线程。


2.3 结论

  虽然信号回调函数与主线程是同一线程,但当主线程捕捉到信号时,主线程的执行任务会被挂起,转而去执行处理信号,并执行信号回调函数。即是产生了软中断。因此,如果主线程持有锁,此时有信号进来,CPU去处理信号回调函数;函数中由于申请不到锁资源,处于等待状态;主线程因为软中断(信号回调函数)未退出,而一直处于上锁状态。两者一直在等待资源,形成了死锁。


3 避免死锁

  既然我们知道这种情况易产生死锁,避免死锁才是我们的根本目的。而避免死锁方式是回调函数中禁止使用锁,或者以其他方式替换处理。参考以下方式。

  • 使用自旋锁( spinlock )代替互斥锁(mutex),程序进入了spinlock临界区,中断是会被关闭的;即是spinunlock后才会捕捉到信号,避免了死锁;
  • 新建一个信号处理线程,把信号回调任务由该线程处理;信号处理线程循环调用sigwait(sigtimedwait)同步信号;
  • 创建线程时,调用 pthread_sigmask 设置本线程的信号掩码,屏蔽信号捕捉。

  处理复杂的任务,建议使用第二种方式。


  除此之外,一个严谨的信号处理回调函数,应该遵循以下基本原则:

  • 信号回调函数尽可能简单,确保尽快退出函数,与中断处理函数原则一样;
  • 信号回调函数不能调用不可重入函数和线程不安全函数,如mallocfreeprintf、标准I/O函数;
  • 信号回调函数访问全局变量时,变量需加volatile修饰,避免编译器优化。

4 举一反三

  通过上面的分析,回调函数禁止使用互斥锁。但是,一些库函数、第三方SDK、开发成员写的模块等,函数内部可能使用了互斥锁,使用时需格外注意,因为这些函数没有显式申请互斥锁,如果出现问题,将会增加查找问题的难度,无法直接通过审查代码初步发现<。比如,C库中常见的线程安全函数(内部加锁),这些函数在回调函数中使用时需格外注意:

  • localtime_r,时间转换函数,localtime返回的是静态变量,不是线程安全函数,多线程访问时,值可能会被修改;后C库提供localtime_r线程安全函数,实际是内部加了锁;
  • rand_r,随机数生成函数;
  • strtok_r,字符串分割函数;
  • asctime_rctime_r,时间格式化为字符函数;
  • gethostbyaddr_rgethostbyname_r,主机名称和地址转换函数。

5 死锁例子代码

#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <signal.h>
#include <sys/syscall.h>
#include "pthread.h" 

pthread_mutex_t mutex;

void signal_handle(int signo) 
{
	pthread_mutex_lock(&mutex);
	printf("Signal thread id:0x%lx\n", syscall(SYS_gettid));
	pthread_mutex_unlock(&mutex);
	return; 
}

int main(int argc, char *argv)
{
	uint32_t sys = 0;
	
	signal(SIGALRM, signal_handle);
	pthread_mutex_init(&mutex, NULL);
	printf("Main thread id:0x%lx\n", syscall(SYS_gettid));
	for(;;)
	{
		pthread_mutex_lock(&mutex);
		printf("sys [%d]\n", sys++);
		if (sys == 3)
		{
			alarm(1);
			sleep(1);	/* 故意延迟,产生死锁 */
		}
		if (sys == 5)
		{
			pthread_mutex_unlock(&mutex);
			break;
		}
		pthread_mutex_unlock(&mutex);
		sleep(1);
	}
	return 0; 
}

6 参考文章

【1】Linux 多线程应用中如何编写安全的信号处理函数

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Linux应用编程】一个异步信号处理引起死锁问题的思考 的相关文章

随机推荐

  • QT 上位机(网络通信)

    Client类 span class hljs comment span class hljs xmlDocTag span span class hljs xmlDocTag span span class hljs xmlDocTag
  • win10系统如何通过ssh远程登录另一台电脑的deepin linux系统

    1 首先 xff0c 更新一下软件源 xff0c 打开 终端窗口 xff0c 输入 sudo apt get update 2 然后 xff0c 在终端中 xff0c 输入 sudo apt get install openssh serv
  • 基于STM32与ESP8266的太空人WiFi天气时钟(代码开源)

    前言 xff1a 本文为手把手教学ESP8266著名开源项目 太空人WiFi天气时钟 xff0c 不同的是本次项目采用的是STM32 作为MCU 两者开发过程中有因为各自芯片的特点 xff08 时钟频率 xff0c 内存大小等 xff09
  • make menuconfig错误的解决办法

    如果使用make menuconfig的方式配置内核 xff0c 又碰巧系统没有安装ncurses库 xff08 ubuntu系统 默认就没有安装此库 xff09 xff0c 就会出现错误 xff0c 错误信息大体上如下 xff1a Una
  • FileZilla以root用户登录Linux

    一 首先创建root用户的密码 span class hljs built in sudo span passwd root 然后输入要设置的密码 xff0c 然后再输入一次 xff0c 成功 xff01 二 修改配置文件 filezila
  • px4编译

    如果下载速度特别慢 xff0c 可以使用手机的4G网络 位置确定 mkdir p src cd src 开始下载指定版本的px4 xff0c 在这里是v1 8 2版本 git clone b v1 8 2 https github com
  • px4源码----位置估算(position_estimator_inav_params.h)

    pragma once include lt parameters param h gt struct position estimator inav params float w z baro 权重 z轴 气压计位置 0 5 float
  • ubuntu如何把调整cpu策略

    一 安装cpu频率管理软件 sudo apt get install cpufrequtils 二 查看cpu当前状态 cpufreq info 其中available cpufreq governors xff1a performance
  • 树莓派系统介绍

    树莓派是一个微型计算机 xff0c 和普通的电脑没有什么区别 xff0c 只是体积更小 xff0c 只有卡片大小 xff0c 存储能力和计算能力会差一点 xff0c 主要用于学习 xff0c 实验所用 是电脑就要安装操作系统 xff0c 树
  • 小觅摄像头ROS编译错误

    GitHub slightech MYNT EYE ORB SLAM2 Sample Forked from ORB SLAM2 https github com raulmur ORB SLAM2 Forked from ORB SLAM
  • 华为路由器交换机常用命令(随时补充更新)

    一 视图切换 lt huawei gt 用户视图 huawei 系统视图 xff0c 在用户视图状态下输入sys进入 xff0c 在系统视图下输入quit或者return返回用户视图 huawei g0 0 1 端口视图 xff0c 从系统
  • 01路径规划问题的相关理论

    目录 1 旅行商问题 2 有能力约束的车辆路径问题 3 车辆路径主要要素特征 4 约束条件分析 5 带时间窗的车辆路径问题 6 车辆路径问题求解算法 7 小节 1 旅行商问题 旅行商问题 xff08 Traveling Saleman Pr
  • 时序数据库-3-[IoTDB]的安装与使用

    IoTDB官方文档手册 Apache IoTDB xff08 物联网数据库 xff09 是一体化收集 存储 管理与分析物联网时序数据的软件系统 Apache IoTDB 采用轻量式架构 xff0c 具有高性能和丰富的功能 xff0c 并与A
  • 【强烈推荐】基于STM32的TFT-LCD各种显示实现(内容详尽含代码)

    前言 xff1a TFT LCD模块作为人们日常生活中常见屏幕类型之一 xff0c 使用的受众面非常广阔 例如 xff1a 显示各个传感器数值 xff0c 显示精美界面 xff0c 多级化菜单系统等等都不离不开他的身影 可以说学会TFT L
  • 时序数据库-4-[IoTDB]的python3操作

    从采集到存储 xff1a 时序数据库到底怎么处理时间 xff1f iotdb官方文档手册 1 容器安装iotdb 可以使用docker volume create命令创建 docker 卷 此命令将在 var lib docker volu
  • [汇总]基于ESP32的四旋翼无人机开发纪实

    文章目录 一 项目说明1 已实现功能2 硬件配置 二 ESPlane2 0 开发笔记三 相关传感器驱动移植四 参考链接 ESPlane 项目更名为 ESP Drone 现已公开代码仓库和文档 代码仓库 xff1a https github
  • [填坑]Ubuntu安装显卡专有驱动后鼠标键盘无法使用

    问题描述 我在两个地方遇到了同样的问题 xff0c 解决方法也如出一辙 xff0c 由于没有研究源码 xff0c 暂不清楚原因 问题1描述 xff1a 为了解决Ubuntu下笔记本功耗问题 xff0c 在网友建议下我安装了bumblebee
  • uniapp-前后端开发app-系列01开篇

    系列文章目录 文章目录 系列文章目录前言一 开发工具 xff1f 二 项目架构三 具体内容实现 前言 提示 xff1a 这里可以添加本文要记录的大概内容 xff1a 随着app和小程序的发展 有没有开发一个模版 其他端程序都能用 uniap
  • TypeError: iter() returned non-iterator of type

    在使用Python迭代器时出现错误 xff1a class Fibs def init self self a 61 0 self b 61 1 def next self self a self b 61 self b self a 43
  • 【Linux应用编程】一个异步信号处理引起死锁问题的思考

    文章目录 1 前言2 为什么会产生死锁2 1 死锁2 2 分析2 3 结论 3 避免死锁4 举一反三5 死锁例子代码6 参考文章 1 前言 最近在维护别人的代码时 xff0c 遇到一个线程死锁问题 xff0c 一番折腾 xff0c 最终定位