C++ 死锁检测基础思路详解

 更新时间:2026年03月04日 14:51:40   作者:幽默代码人  
文章介绍了C++中死锁检测的基本思路,包括理论部分的死锁概念、产生条件以及检测方法,以及实现部分的核心数据结构、逻辑和钩子机制,感兴趣的朋友跟随小编一起看看吧

一、理论部分

死锁(Deadlock)是并发编程中最棘手的问题之一。不同于内存泄漏可以通过工具最终定位,死锁一旦发生,往往导致系统彻底卡死,且难以复现。

死锁的现象举一个简单的例子,如下图所示,3个线程都在运行,且图中资源均一次只能被一个线程占用,线程A占用资源1,线程B占用资源2,线程C占用资源3,此时线程A不释放资源1且想去占用资源2,而线程B也不释放资源2并且想去占用资源3,而线程C同样不释放资源3去占用资源1。这样,线程A, B, C 都因为获取不到足够的资源而一直陷入等待状态。这种现象就是死锁。

死锁产生的四个必要条件,也就是死锁产生的原因如下,缺一不可:

条件说明
互斥条件资源一次只能被一个线程占用
持有且等待线程持有资源同时请求新资源
不可抢占资源不能被强制释放
循环等待形成线程-资源的循环链

这四个必要条件,只要打破一个,就不会形成死锁,但一般来说我们不会去打破第一个互斥条件,因为这一般是资源自带的性质,我们无法避免。比如说买票时的车票数,不同人看到的剩余票数应该是一致的,这无法避免。

而要打破死锁,首先需要的是检测到死锁。那么如何检测呢?回到刚才的图我们可以发现,形成死锁后图中出现了环,也就是说我们可以将线程与资源占用关系抽象成图之后,检测图中是否形成环回路,只要有环,那就出现了死锁,进而采取下一步操作。

二、实现部分

我们在此仅实现一个简易化的版本,由于理解死锁检测。

1. 数据结构设计

核心数据结构

struct source_type {
    uint64 id;          // 线程ID或锁地址
    enum Type type;     // 类型:PROCESS 或 RESOURCE(虽然代码中只用到了PROCESS)
    uint64 lock_id;     // 锁ID(用于locklist)
    int degress;        // 锁的等待计数
};
struct vertex {
    struct source_type s;  // 顶点数据
    struct vertex *next;   // 邻接表指针
};

任务图(等待图)

struct task_graph {
    struct vertex list[MAX];      // 顶点数组(邻接表头)
    int num;                      // 顶点数量
    struct source_type locklist[MAX]; // 锁持有表
    int lockidx;                  // 锁数量
    pthread_mutex_t mutex;        // 保护图结构的锁(实际未使用)
};
  • 邻接表:list[MAX] 存储所有线程顶点,next 指向该线程等待的其他线程
  • 锁持有表:记录每个锁当前被哪个线程持有

2. 核心逻辑

核心规则

代码的逻辑是当线程1想要持有锁时,先查询锁持有表,如果锁没有被占用,那就直接使用锁并在锁持有表中新增一条对应的记录。如果锁被占用了,就在图中连一条指向占有线程2的边。

当线程T1试图获取已被T2持有的锁L时:
    添加边:T1 → T2
    表示T1在等待T2释放锁

三个关键函数(部分伪代码)

// 1. 加锁前:如果锁已被其他线程持有,建立等待关系
void lock_before(tid, lockaddr) {
    if (锁已被其他线程T2持有) {
        添加边:当前线程T1 → T2
        lock.degress++  // 等待计数增加
    }
}
// 2. 加锁后:更新锁的持有者
void lock_after(tid, lockaddr) {
    if (锁是空闲的) {
        记录当前线程持有该锁
    } else {
        移除之前建立的等待边(因为已经获得锁)
        更新锁的持有者为当前线程
    }
}
// 3. 解锁后:如果没人等待,清空锁记录
void unlock_after(tid, lockaddr) {
    if (锁的degress == 0) {
        清空锁的持有信息
    }
}

3. 死锁检测算法

在以上接口的基础上,我们再添加一个检测图中环的算法就能实现死锁检测。最暴力的做法是使用DFS 但不推荐。推荐使用 Tarjan 算法来检测环,一个环一定是一个有向图的一个强连通分量,通过这个性质来实现死锁检测。

4. 钩子机制(Hooking)

最后是通过钩子机制获取并修改原始函数指针,将pthread_mutex_lock和pthread_mutex_unlock改写逻辑:

// hook
// define
typedef int (*pthread_mutex_lock_t)(pthread_mutex_t *mutex);
pthread_mutex_lock_t pthread_mutex_lock_f = NULL;
typedef int (*pthread_mutex_unlock_t)(pthread_mutex_t *mutex);
pthread_mutex_unlock_t pthread_mutex_unlock_f = NULL;
// implement
int pthread_mutex_lock(pthread_mutex_t *mutex) {
	pthread_t selfid = pthread_self();
	lock_before((uint64_t)selfid, (uint64_t)mutex);
	pthread_mutex_lock_f(mutex);
	lock_after((uint64_t)selfid, (uint64_t)mutex);
}
int pthread_mutex_unlock(pthread_mutex_t *mutex) {
	pthread_mutex_unlock_f(mutex);
	pthread_t selfid = pthread_self();
	unlock_after((uint64_t)selfid, (uint64_t)mutex);
}
// init
void init_hook(void) {
	if (!pthread_mutex_lock_f)
		pthread_mutex_lock_f = dlsym(RTLD_NEXT, "pthread_mutex_lock");
	if (!pthread_mutex_unlock_f)
		pthread_mutex_unlock_f = dlsym(RTLD_NEXT, "pthread_mutex_unlock");
}

以上是死锁检测的一个基本思路,将线程与资源及其关系抽象成有向图,对图进行环回路检测。而在使用死锁检测时,可以用一个独立的线程监控,不影响主程序性能。

到此这篇关于C++ 死锁检测基础思路的文章就介绍到这了,更多相关C++ 死锁检测内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++ push方法与push_back方法常见方法介绍

    C++ push方法与push_back方法常见方法介绍

    push与push_back是STL中常见的方法,都是向数据结构中添加元素,本文还将简述push对应的stack与queue系列,常见方法的介绍,以及与push_back相对应的vector系列常见方法介绍,感兴趣的朋友跟随小编一起看看吧
    2022-11-11
  • C语言实现房屋管理系统

    C语言实现房屋管理系统

    这篇文章主要为大家详细介绍了C语言实现房屋管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-12-12
  • C++ Boost Intrusive库示例精讲

    C++ Boost Intrusive库示例精讲

    Boost是为C++语言标准库提供扩展的一些C++程序库的总称。Boost库是一个可移植、提供源代码的C++库,作为标准库的后备,是C++标准化进程的开发引擎之一,是为C++语言标准库提供扩展的一些C++程序库的总称
    2022-11-11
  • 零基础学习C/C++需要注意的地方

    零基础学习C/C++需要注意的地方

    这篇文章主要介绍了零基础学习C/C++需要注意的地方,文中讲解非常细致,供大家参考和学习,想要学习C/C++的可以阅读此文
    2020-06-06
  • C/C++经典杨辉三角问题解决方案

    C/C++经典杨辉三角问题解决方案

    杨辉三角形,又称帕斯卡三角形、贾宪三角形、海亚姆三角形,它的排列形如三角形。本文将为大家介绍通过C++/C语言实现打印杨辉三角形的示例代码,需要的可以参考一下
    2023-02-02
  • Qt数据库应用之实现数据的导入与导出

    Qt数据库应用之实现数据的导入与导出

    QT中涉及到数据库相关的项目,几乎都需要将少量的信息数据导出到文件保存好,然后用户可以打开该表格进行编辑,编辑完成后保存,再重新导入到软件中。所以本文将具体为大家介绍一下这一功能如何实现,感兴趣的可以跟随小编一起试一试
    2022-01-01
  • VScode+ESP32简单环境搭建

    VScode+ESP32简单环境搭建

    本文章向大家介绍ESP32-C3搭建环境教程,主要包括ESP32-C3搭建环境教程使用实例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-06-06
  • C++基于CreateToolhelp32Snapshot获取系统进程实例

    C++基于CreateToolhelp32Snapshot获取系统进程实例

    这篇文章主要介绍了C++基于CreateToolhelp32Snapshot获取系统进程实例,是Windows应用程序设计中非常实用的技巧,需要的朋友可以参考下
    2014-10-10
  • C语言零基础入门(1)

    C语言零基础入门(1)

    这篇文章主要为大家详细介绍了C语言零基础入门的方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-03-03
  • C++利用LuaIntf调用Lua的方法示例

    C++利用LuaIntf调用Lua的方法示例

    这篇文章主要给大家介绍了关于C++利用LuaIntf调用Lua以及利用lua-intf来调用C++函数的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一的参考学习价值,需要的朋友们下面来一起看看吧。
    2017-11-11

最新评论