当前位置: 首页 > news >正文

死锁 详解

目录

前言

一、什么是死锁

二、死锁产生的四个必要条件

1. 互斥条件

2. 请求与保持条件

3. 不剥夺条件

4. 循环等待条件

三、经典死锁示例

四、如何避免死锁?

例方法1:破坏循环等待(锁顺序)

例方法2:破坏持有并等待(一次性申请)

例方法3:破坏不可剥夺(超时)


前言

死锁是指多个线程因争夺资源而互相等待的阻塞现象。

其产生的四个必要条件是:互斥条件、请求与保持条件、不剥夺条件和循环等待条件。

本文通过示例代码展示了两个线程因获取锁的顺序不同而导致的死锁情况。

解决方法包括固定锁获取顺序、使用trylock或超时锁等。

避免死锁的关键在于打破四个必要条件之一,如一次性分配所有资源或按固定顺序获取锁。


一、什么是死锁

死锁指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干预,它们都将无法继续执行。

举个生活中的例子:

线程A拿着锁1,等待锁2
线程B拿着锁2,等待锁1
两者互相等待,谁也无法继续执行

二、死锁产生的四个必要条件

1. 互斥条件

资源在同一时刻只能被一个线程占用。

2. 请求与保持条件

线程已经持有至少一个资源,同时又请求其他资源,且不释放已持有的资源。

3. 不剥夺条件

线程已获得的资源在未使用完之前,不能被其他线程强行剥夺。

4. 循环等待条件

多个线程之间形成一种头尾相接的循环等待资源关系。

三、经典死锁示例

#include <stdio.h> #include <pthread.h> #include <unistd.h> // 定义两个互斥锁,并初始化为默认属性 // PTHREAD_MUTEX_INITIALIZER 是静态初始化宏,相当于调用 pthread_mutex_init 使用默认属性 pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER; /** * 线程1 的执行函数 * 它会先锁定 mutex1,然后尝试锁定 mutex2,但中间 sleep 1 秒, * 这使得线程2 有机会先锁定 mutex2,从而形成死锁。 */ void *thread1_func(void *arg) { printf("线程1: 尝试获取互斥锁1...\n"); pthread_mutex_lock(&mutex1); // 获取锁1,成功则继续,否则阻塞 printf("线程1: 获得了互斥锁1\n"); // 模拟一些操作,增加死锁概率 // 休眠1秒,让线程2 有机会获得锁2 sleep(1); printf("线程1: 尝试获取互斥锁2...\n"); // 尝试获取锁2,此时锁2可能已被线程2 持有,因此线程1 阻塞,等待线程2 释放锁2 pthread_mutex_lock(&mutex2); // 等待线程2释放锁2 printf("线程1: 获得了互斥锁2\n"); // 执行操作(临界区) // 这里可以放共享资源的访问代码 // 释放锁,顺序与加锁相反 pthread_mutex_unlock(&mutex2); pthread_mutex_unlock(&mutex1); return NULL; } /** * 线程2 的执行函数 * 它会先锁定 mutex2,然后尝试锁定 mutex1,与线程1 的加锁顺序相反, * 从而造成循环等待的死锁局面。 */ void *thread2_func(void *arg) { printf("线程2: 尝试获取互斥锁2...\n"); pthread_mutex_lock(&mutex2); // 获取锁2 printf("线程2: 获得了互斥锁2\n"); sleep(1); // 确保线程1 已经获得锁1 printf("线程2: 尝试获取互斥锁1...\n"); // 尝试获取锁1,此时锁1被线程1 持有,线程2 阻塞,等待线程1 释放锁1 pthread_mutex_lock(&mutex1); // 等待线程1释放锁1 printf("线程2: 获得了互斥锁1\n"); pthread_mutex_unlock(&mutex1); pthread_mutex_unlock(&mutex2); return NULL; } int main() { pthread_t tid1, tid2; // 创建两个线程,分别执行 thread1_func 和 thread2_func pthread_create(&tid1, NULL, thread1_func, NULL); pthread_create(&tid2, NULL, thread2_func, NULL); // 等待两个线程结束,但由于死锁,它们将永远无法结束,所以程序会卡在这里 pthread_join(tid1, NULL); pthread_join(tid2, NULL); return 0; }

死锁产生的原因分析:
1. 线程1 持有 mutex1,等待 mutex2。
2. 线程2 持有 mutex2,等待 mutex1。
3. 两个线程互相等待对方释放自己需要的锁,且不会主动释放已持有的锁,
形成了循环等待条件,满足死锁的四个必要条件(互斥、持有并等待、不可剥夺、循环等待)。

解决方法示例:

  • 固定锁的获取顺序(例如总是先锁 mutex1 再锁 mutex2)。
  • 使用 pthread_mutex_trylock 尝试加锁,若失败则释放已持有的锁并重试。
  • 使用超时锁(如 pthread_mutex_timedlock)。

四、如何避免死锁?

死锁的概念

多个进程或线程访问一组竟态资源的时候,出现的永久阻塞的问题。

也可以这么说:指两个或两个以上的线程或进程在执行程序的过程中,因争夺资源或者程序推进顺序不当而相互等待的一个现象

产生的原因主要有三个:系统资源不足,程序运行推进的顺序不当,资源分配不当。

或者说:死锁产生的必要条件是:互斥条件、请求和保持条件、不剥夺条件、环路等待条件.

避免死锁就是打破这四个条件中的某一个即可。如某个进程申请多个资源,只要有一个资源不满足暂时就不要分配任何资源,等所有资源能满足时一起分配。

例方法1:破坏循环等待(锁顺序)

所有线程按照相同的顺序获取锁。

// 线程1和线程2都先锁A再锁B void* thread1(void* arg) { pthread_mutex_lock(&lockA); pthread_mutex_lock(&lockB); // ... } void* thread2(void* arg) { pthread_mutex_lock(&lockA); // 与线程1顺序一致 pthread_mutex_lock(&lockB); // ... }

例方法2:破坏持有并等待(一次性申请)

使用 trylock 或一次性申请所有资源。

void* thread(void* arg) { while (1) { pthread_mutex_lock(&lockA); if (pthread_mutex_trylock(&lockB) == 0) { break; // 同时获得两个锁 } pthread_mutex_unlock(&lockA); // 释放锁A,避免死锁 usleep(100); // 稍后重试 } // 操作... pthread_mutex_unlock(&lockB); pthread_mutex_unlock(&lockA); }

例方法3:破坏不可剥夺(超时)

使用带超时的锁函数,超时后释放已持有的锁

使用 C++11 标准库的 timed_mutex。

std::timed_mutex:提供 try_lock_for(相对时间)和 try_lock_until(绝对时间)方法。

示例1:

#include <iostream> #include <thread> #include <mutex> #include <chrono> // 使用 timed_mutex 替代普通的 mutex std::timed_mutex mtx1, mtx2; void thread_func() { // 尝试获取第一个锁,等待1秒 if (mtx1.try_lock_for(std::chrono::seconds(1))) { std::cout << "线程 " << std::this_thread::get_id() << " 获得锁1" << std::endl; // 模拟一些操作 std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 尝试获取第二个锁,等待1秒 if (mtx2.try_lock_for(std::chrono::seconds(1))) { std::cout << "线程 " << std::this_thread::get_id() << " 获得锁2,执行操作" << std::endl; // 临界区操作 std::this_thread::sleep_for(std::chrono::milliseconds(100)); mtx2.unlock(); // 释放锁2 } else { std::cout << "线程 " << std::this_thread::get_id() << " 获取锁2超时,释放锁1" << std::endl; } mtx1.unlock(); // 释放锁1 } else { std::cout << "线程 " << std::this_thread::get_id() << " 获取锁1超时" << std::endl; } } int main() { std::thread t1(thread_func); std::thread t2(thread_func); t1.join(); t2.join(); return 0; }

示例2:使用 try_lock_until 指定绝对时间点

#include <iostream> #include <thread> #include <mutex> #include <chrono> std::timed_mutex mtx1, mtx2; void thread_func() { auto timeout_point = std::chrono::steady_clock::now() + std::chrono::seconds(2); // 尝试在指定时间点前获取锁 if (mtx1.try_lock_until(timeout_point)) { std::cout << "线程 " << std::this_thread::get_id() << " 获得锁1" << std::endl; // 更新超时点 timeout_point = std::chrono::steady_clock::now() + std::chrono::seconds(1); if (mtx2.try_lock_until(timeout_point)) { std::cout << "线程 " << std::this_thread::get_id() << " 获得锁2" << std::endl; // 临界区 std::this_thread::sleep_for(std::chrono::milliseconds(100)); mtx2.unlock(); } mtx1.unlock(); } }

http://www.jsqmd.com/news/503446/

相关文章:

  • ai coding工具共性(四)skill
  • 从ENVI FLAASH到地表参量反演:一份完整的遥感数据处理实战指南
  • yz-女生-角色扮演-造相Z-Turbo与Python爬虫结合:自动采集并生成动漫角色数据集
  • 从零到一:在Ubuntu 18.04上构建PX4-Autopilot开发环境全攻略
  • Cosmos-Reason1-7B数据库设计助手:基于MySQL的智能ER图生成与优化
  • AMD SMU调试工具深度解析:实现处理器性能调优的终极指南
  • 电源设计必看:X/Y电容选型避坑指南(附漏电流计算公式)
  • GPU Power Brake设置全攻略:主动与被动模式详解及性能影响实测
  • ArcGIS进阶:从数据到洞察,土地利用时空演变分析与可视化全流程
  • 从Docker Compose到生产环境:我的DolphinScheduler高可用架构演进实录
  • Aprilgrid标定板参数详解:如何选择最适合你的tsize和tspace?
  • 2025美赛论文排版终极指南:从Word到LaTeX的5种O奖模板实战
  • Claude Skills大揭秘:让你的AI不仅能说会道,更能高效执行!
  • 社区生鲜买菜小程序前端功能版块设计及玩法介绍
  • 开启图像处理之旅:C# 与 OpenCV 的奇妙结合
  • Dva + ECharts 实战:如何优化React大屏项目的性能与可维护性
  • 正则化实战:用Python实现L1和L2正则化并比较它们的实际效果
  • 无人机 RGB+热红外融合检测建筑裂缝与渗漏,34 层高楼约 2 小时
  • 相机标定常见误区解析:为什么你的重投影误差总是降不下来?
  • ROS2新手必看:解决‘无法定位软件包‘错误的5个实用技巧(含rosdep常见问题)
  • 一天一个开源项目(第55篇):Spec Kit - GitHub 开源的规范驱动开发工具包
  • YOLO12与增强现实结合:实时物体标注系统
  • 别再被坐标系搞晕了!UniApp中getLocation的WGS84与GCJ02区别详解及实战转换方案
  • 告别卡顿!G-Helper:华硕笔记本玩家的终极性能优化神器
  • 使用ROS1和Pycharm高效转换Realsense相机bag文件为MP4格式
  • Android Media3实战:从ExoPlayer集成到自定义播放器开发(附完整代码)
  • 2026年3月优质的河北铸铁闸门厂家选择指南:平面、拱形、铸铁镶铜、双向止水、机闸一体铸铁闸门厂家 - 海棠依旧大
  • 虚拟经济典狱长:软件测试工程师的NFT破产富豪监管之道
  • Genanki:用Python批量生成Anki卡片的5个核心技能
  • 广州高考复读学校人性化管理解析及10所优质学校盘点 - 妙妙水侠