跟我学C++中级篇——控制死锁

一、同步和死锁

在前面学习多线程和网络编程时,都对线程中数据的同步和数据结构多线程访问的安全问题进行了分析和说明。其实,多线程编程之所以难,难点之一就在这里,数据同步意味着效率和安全的平衡,而这里的安全有一个重要的关节就在于死锁。
所以如果能很好的掌握线程间的同步以及防止出现死锁这些问题后,基本对多线程的理解和控制就算是入了门。

二、死锁场景

死锁其实在大多数的开发者实际的开发中遇到的并不多。即使遇到也是在应用层面上用到,很少有主动写出死锁的。但这不代表死锁少见,一般来说,常见的死锁出现的场景有以下几方面:
1、数据库中的事务处理
比如多个事务都需要同时处理多张表,这里假在锁定一些表后,有可能导致死锁
2、多线程编程
这种情况下刚刚也提到了,较少有开发者会写出这种死锁的代码,主要原因是,一般的开发场景也不会有多个资源在线程间同时访问,即使有,很多开发者也会分开处理,等待通知后再进行
3、在办公中访问一些打印机等专享资源
这种情况其实比较常见,有时候儿打印机(包括文件等)啥的与OS或网络的交互会犯傻,这时个儿往往重启一下就好了
4、分布式应用中的同步请求
这种情况在分布式应用中比较常见,比如P2P中的互相通信如果操作不当,就有可能产生死锁
5、大规模计算中的资源分配
比如在一些大规模计算中使用计算图,就有可能在图的处理中资源分配出现死锁问题

三、避免死锁

要想避免死锁就必须了妥死锁的条件,学习过操作系统相关知识的开发者都知道,死锁的四个条件:
1、资源互斥
这个比较容易理解,资源必须是独占的,即一个资源只能被一个线程占用。这就和食堂排队打汤的一样,大勺只有一个,只有一个人打好放勺子才可能另外一个人去使用
2、互相持有
也叫占有和等待,即一个线程完成一项工作需要两个以上的资源,已经占有了一个,申请另外一个时,发现另外一个资源被其它线程占有,它会继续申请而不释放自己持有的资源。这个更好理解,就比如集一些类似火花、邮票或卡之类的东西(假设每个都是唯一的),如果三个人分别持有一套的三部分,大家都会寻找其它的而不会把自己的部分放弃
3、不可剥夺
即任何一个线程持有的资源无法被其它线程强制获取。这个好理解,正常情况下,公民的财产不会被其它公民强行取得
4、循环等待
这个理解也不难,就是多个线程形成了一个环状的资源持有,即A线程持有B线程需要的资源,B线程持有C线程的需要的资源…N线程持有A线程需要的资源。这就是书本上讲的哲学家进餐的问题。
既然知道了死锁造成的原因,那么解决死锁就必须从上面的原因中找方法,只要打破任何一个条件,死锁也就不存在了,即:
1、打破资源互斥,即将资源设计为允许多个线程访问
2、打破互相持有,即所有的线程申请资源时一次性申请完成,要么成功,要么失败,也或者可以在申请前把自己持有的资源释放
3、打破不可剥夺,即允许在指定条件下获取某些线程占有的资源
4、打破循环等待,即对资源进行有序控制不允许随机申请,这样就会断开循环的链条

四、死锁的解决方案

知道了避免死锁的方法,就可以探讨解决死锁的编程方案了。一般来说,解决死锁的方案有以下几种:
1、无锁编程
这种最容易理解了,打不过就躲过嘛。无锁编程在前面分析说明了很多,此处不再展开
2、使用超时锁
这个可以打破占有和等待。在C++11中提供了一些新的锁如 std::timed_mutex 和 std::recursive_timed_mutex,看下面的例子:

#include <chrono>
#include <iostream>
#include <mutex>
#include <sstream>
#include <thread>
#include <vector>using namespace std::chrono_literals;std::mutex cout_mutex; // 控制到 std::cout 的访问
std::timed_mutex mutex;void job(int id)
{std::ostringstream stream;for (int i = 0; i < 3; ++i){if (mutex.try_lock_for(100ms)){stream << "成功 ";std::this_thread::sleep_for(100ms);mutex.unlock();}elsestream << "失败 ";std::this_thread::sleep_for(100ms);}std::lock_guard<std::mutex> lock{cout_mutex};std::cout << "[" << id << "] " << stream.str() << "\n";
}int main()
{std::vector<std::thread> threads;for (int i = 0; i < 4; ++i)threads.emplace_back(job, i);for (auto& i: threads)i.join();
}

3、可以将锁排序
用来打破循环等待等条件,这种应用比较简单,看下面的例子:

#include <iostream>
#include <mutex>
#include <thread>
#include <unistd.h>std::mutex m1;
std::mutex m2;void taskFunc(bool order) {if (order) {// reversestd::lock_guard<std::mutex> lock2(m2);sleep(2);std::lock_guard<std::mutex> lock1(m1);std::cout << "lock risky!\n";} else {// orderstd::lock_guard<std::mutex> lock1(m1);sleep(2);std::lock_guard<std::mutex> lock2(m2);std::cout << "lock safe!\n";}
}int main() {std::thread t1([]() { taskFunc(true); });std::thread t2([]() { taskFunc(false); });t1.join();t2.join();return 0;
}

上面的代码会产生死锁,可以强制进行结束。
4、一次性获取锁
在C++17中提供了std::scoped_lock,相关的代码可以查看前面的文章“跟我学C++中级篇——std::scoped_lock”
5、控制资源访问
这种就方法就比较多了,比如在Windows平台可以使用事件控制而在Linux平台上使用条件变量进行,即没有收到通知的一方不能去寻求资源的控制。典型的就是在Nginx的网络资源获取中就采用了这种控制的手段

如果在编程时没有注意,在实际的应用中出现了意外,可以使用一些工具来检查是否存在死锁:
1、使用GDB
在编译后,使用gdb:

gdb ./lockOrder
(gdb) r
Starting program: /home/fpc/qt65_project/lockOrder/lockOrder 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff4eff640 (LWP 843994)]
[New Thread 0x7ffff46fe640 (LWP 843995)]
[New Thread 0x7ffff3efd640 (LWP 843996)]
//此处如果不退出可以ctrl+C强制退出来
(gdb) info threadId   Target Id                                      Frame 
* 1    Thread 0x7ffff7317340 (LWP 843991) "lockOrder" __futex_abstimed_wait_common64 (private=128, cancel=true, abstime=0x0, op=265, expected=843995, futex_word=0x7ffff46fe910) at ./nptl/futex-internal.c:572    Thread 0x7ffff4eff640 (LWP 843994) "lockOrder" 0x00007ffff6ce57f8 in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0, req=req@entry=0x7ffff4ebf150, rem=rem@entry=0x0) at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:783    Thread 0x7ffff46fe640 (LWP 843995) "lockOrder" futex_wait (private=0, expected=2, futex_word=0x55555555c160 <m1>) at ../sysdeps/nptl/futex-internal.h:1464    Thread 0x7ffff3efd640 (LWP 843996) "lockOrder" futex_wait (private=0, expected=2, futex_word=0x55555555c1a0 <m2>) at ../sysdeps/nptl/futex-internal.h:146
(gdb) 

这样就可以看到具体的死锁的情况了
2、使用valgrind并启用Helgrind工具
执行下列命令可得到相关日志:

valgrind --tool=helgrind --log-file=log.txt  ./lockOrder 

日志显示(未贴全):

Thread #3: Exiting thread still holds 1 lock
==849869==    at 0x4B572C0: futex_wait (futex-internal.h:146)
==849869==    by 0x4B572C0: __lll_lock_wait (lowlevellock.c:49)
==849869==    by 0x4B5E001: lll_mutex_lock_optimized (pthread_mutex_lock.c:48)
==849869==    by 0x4B5E001: pthread_mutex_lock@@GLIBC_2.2.5 (pthread_mutex_lock.c:93)
==849869==    by 0x485051D: mutex_lock_WRK (hg_intercepts.c:935)
==849869==    by 0x4854CEE: pthread_mutex_lock (hg_intercepts.c:958)
==849869==    by 0x109DE1: __gthread_mutex_lock(pthread_mutex_t*) (in /home/fpc/qt65_project/lockOrder/build/lockOrder)
==849869==    by 0x109E65: std::mutex::lock() (in /home/fpc/qt65_project/lockOrder/build/lockOrder)
==849869==    by 0x10A09D: std::lock_guard<std::mutex>::lock_guard(std::mutex&) (in /home/fpc/qt65_project/lockOrder/build/lockOrder)
==849869==    by 0x109456: taskFunc(bool) (in /home/fpc/qt65_project/lockOrder/build/lockOrder)
==849869==    by 0x109539: main::{lambda()#2}::operator()() const (in /home/fpc/qt65_project/lockOrder/build/lockOrder)
...

3、ThreadSanitizer (TSan)
它是集成于GCC和Clang的检测工具,使用方法如下:

g++ -fsanitize=thread -g -o lockorder main.cpp
./lockorder

4、Lttng
这个没有实验成功,暂时没找到原因。不过据说这个挺好用
5、其它
在不同的平台上可能有不同的工具,比如在Windows平台上的有名的工具Windbg和VS(ASan)等,安卓平台上也有类似的工具;另外像前面介绍的Perf Tools工具,也可以间接的辅助进行线程死锁的定位

五、总结

死锁虽然在面试时反复被问到,但在实践中真正写出来或者遇到的并没有想象的那么多。其实最主要的原因就是大多数的程序员都不会有这种开发的应用场景。但恰恰因为遇到的少,在实际中真正出现时,却不知道从何下手。
还是老规矩,把基础掌握好,会灵活的使用工具。只要发现定位了死锁的问题,就可以根据产生死锁的原因有针对的进行解决即可。没有过不了的火焰山。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/78980.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【matlab】绘制maxENT模型的ROC曲线和omission curve

文章目录 一、maxENT模型二、ROC曲线三、实操3.1 数据提取3.2 绘制ROC曲线3.3 绘制遗漏曲线3.4 多次训练的ROC和测试的ROC 一、maxENT模型 前面的文章已经详细讲过了。 maxENT软件运行后&#xff0c;会生成一个html报告&#xff0c;里面有ROC曲线&#xff0c;但我们往往需要自…

nginx 核心功能

目录 一、基于授权的访问控制 1. 使用 htpasswd 生成用户认证文件 2. 修改 Nginx 主配置文件 二、基于客户端的访问控制 三、Nginx 虚拟主机 1. 基于域名的虚拟主机 2. 基于 IP 的虚拟主机 3. 基于端口的虚拟主机 四、LNMP 架构部署及应用 1. 安装 MariaDB 2. 安装并…

mongoose插入文档,字段类型, 字段验证, 删除文档,更新文档,读取文档,查询文档的条件控制 ,字段筛选,数据排序,数据截取

、Mongoose 中与 文档操作&#xff08;插入、查询、更新、删除&#xff09;及其相关功能&#xff08;字段类型、验证、条件筛选、排序、分页等&#xff09;相关示例&#xff1a; &#x1f4cb; 一、字段类型定义&#xff08;Schema Types&#xff09; const mongoose require…

类和对象 (拷贝构造函数和运算符重载)上

类和对象 (拷贝构造函数和运算符重载)上 拷贝构造函数存在的原因及解决的 C 语言问题 1. 浅拷贝带来的问题 在 C 语言里&#xff0c;当对结构体或者数组进行拷贝操作时&#xff0c;执行的是浅拷贝。所谓浅拷贝&#xff0c;就是单纯地把一个对象的所有成员变量的值复制到另一…

Python深度挖掘:openpyxl和pandas的使用详细

文章目录 一、Excel处理在数据分析中的重要性二、openpyxl基础与核心功能2.1 openpyxl简介与安装2.2 工作簿与工作表的基本操作创建新工作簿打开已有工作簿工作表操作 2.3 单元格操作详解基本单元格操作批量操作单元格特殊单元格操作 2.4 样式与格式设置字体样式对齐方式边框设…

Android Q允许低内存启用系统弹窗

如果SYSTEM_ALERT_WINDOW权限可用&#xff0c;则返回true。 *从Q开始&#xff0c;在低ram手机上禁用SYSTEM_ALERT_WINDOW。 vendor/mediatek/proprietary/packages/apps/MtkSettings/src/com/android/settings/Utils.java public static boolean isSystemAlertWindowEnabled(Co…

taro小程序如何实现大文件(视频、图片)后台下载功能?

一、需求背景 1、需要实现小程序下载最大500M视频 2、同时需支持图片下载 3、退到其他页面再次回到当前页面时&#xff0c;下载进度也需要展示 二、实现步骤 1、在app.ts文件定义一个全局变量globalDownLoadData 2、写一个独立的下载hooks&#xff0c;代码如下&#xff08;…

BUUCTF——Online Tool

BUUCTF——Online Tool 进入靶场 <?phpif (isset($_SERVER[HTTP_X_FORWARDED_FOR])) {$_SERVER[REMOTE_ADDR] $_SERVER[HTTP_X_FORWARDED_FOR]; }if(!isset($_GET[host])) {highlight_file(__FILE__); } else {$host $_GET[host];$host escapeshellarg($host);$host e…

《解锁CSS Flex布局:重塑现代网页布局的底层逻辑》

网页布局作为用户体验的基石&#xff0c;其重要性不言而喻。从早期简单的表格布局&#xff0c;到后来基于浮动与定位的复杂尝试&#xff0c;网页布局技术始终在不断演进。而CSS Flex布局的出现&#xff0c;宛如一颗璀璨的新星&#xff0c;彻底革新了网页布局的设计理念与实践方…

4.28-4.29 Vue

基于数据渲染出用户看到的页面。 常用指令&#xff1a; click单击事件。 axios&#xff1a; 发出请求后&#xff0c;不会等待请求结束&#xff0c;而是继续进行下面的代码。

每日算法-250429

每日 LeetCode 题解 (2025-04-29) 大家好&#xff01;这是今天的 LeetCode 刷题记录&#xff0c;主要涉及几道可以使用贪心策略解决的问题。 2037. 使每位学生都有座位的最少移动次数 题目描述: 思路 贪心 解题过程 要使总移动次数最少&#xff0c;直观的想法是让每个学生…

yolov8+kalman 实现目标跟踪统计人流量

简述 最近接了毕业生的毕业设计题&#xff0c;想着帮帮忙&#xff0c;要使用机器视觉识别&#xff0c;追踪和逻辑统计的方式来统计人流&#xff0c;要求是满足下面特性 高精度&#xff1a;YOLOv8 提供高质量检测&#xff0c;卡尔曼滤波平滑跟踪。高效率&#xff1a;两者结合满…

Shopify网上商店GraphQL Admin接口查询实战

目录 一、Shopify网上商店 二、个人商店配置接口权限 三、PostMan调用接口测试 四、通过Java服务调用接口 一、Shopify网上商店 Shopify是由Tobi Ltke创办的加拿大电子商务软件开发商&#xff0c;总部位于加拿大首都渥太华&#xff0c;已从一家在咖啡店办公的 5人团队&…

【Tips】高效文献管理:Zotero 导入参考文献的多种方式详解

高效文献管理&#xff1a;Zotero 导入参考文献的多种方式详解 在学术研究中&#xff0c;高效管理参考文献是提升效率的关键。Zotero 作为一款强大的文献管理工具&#xff0c;提供了多种便捷的文献导入方式。以下结合文献题录完整性对比分析&#xff0c;为大家详细介绍 Zotero …

[AI]browser-use + web-ui 大模型实现自动操作浏览器

[AI]browser-use web-ui 大模型实现自动操作浏览器 介绍 官方地址&#xff1a;https://github.com/browser-use/web-ui browser-use主要作用是将 AI Agent 与浏览器链接起来从而实现由 AI 驱动的浏览器自动化。今天会给大家介绍如何通过browser-use web-ui来搭建并操作browse…

Springboot请求静态资源时,request.getServletPath() 返回error

大家好&#xff0c;我是 程序员码递夫。 SpringBoot请求静态资源时&#xff0c;request.getServletPath() 返回error&#xff0c; 明明我的目录文件是存在的怎么就报错了呢&#xff1f; 如我请求 http://127.0.0.1:9090/Hanfu/upload/1647161536390.png 通常是因为请求的资…

在开发板上如何处理curl: (60) SSL certificate problem

目录 引言 问题解析 解决方法 跳过证书验证 采用证书认证 结语 引言 最近一直推荐学生们在课程实验中使用curl及其libcurl。curl 是一个强大的命令行工具&#xff0c;用于在命令行中进行数据传输。它支持多种协议&#xff0c;如 HTTP、HTTPS、FTP、FTPS、SCP、SFTP 等。…

CSRF请求伪造

该漏洞主要是关乎于用户&#xff0c;告诫用户不可乱点击链接&#xff0c;提升自我防范&#xff0c;才能不落入Hacker布置的陷阱&#xff01; 1. cookie与session 简单理解一下两者作用 1.1. &#x1f36a; Cookie&#xff1a;就像超市的会员卡 存储位置&#xff1a;你钱包里…

Python循环与遍历详解:从入门到进阶

在Python编程中&#xff0c;循环和遍历是最基础但极其重要的知识点。理解并掌握这部分内容&#xff0c;是编写高效、清晰代码的前提。本文将从for循环和while循环的基本语法出发&#xff0c;逐步深入探讨range、enumerate、zip、列表推导式、字典遍历等Python中常见的遍历技巧&…

Python-MCPServer开发

Python-MCPServer开发 使用FastMCP开发【SSE模式的MCPServer】&#xff0c;熟悉【McpServer编码过程】【McpServer调试方法】 1-核心知识点 1-熟悉【SSE模式的MCPServer】开发2-熟悉【stdio模式的MCPServer】开发3-熟悉【启动MCPServer】的三种方式 3.1-直接启动:python mcp_s…