Linux C/C++ 学习日记(59):手写死锁监测的组件

注:该文用于个人学习记录和知识交流,如有不足,欢迎指点。

一、死锁是什么?

死锁是多线程 / 进程竞争资源时,因形成循环等待且均持有资源不释放,导致所有参与方永久阻塞的状态

二、死锁是如何形成的?

通俗的讲:成环争夺已有锁

(当然还有一种情况是执行完,忘记释放锁了,导致其它线程永远获得不到锁,这种低级错误就没必要去监测了)

如:

线程1和线程2将会永远阻塞住

同理如下:

这四个线程将会永远阻塞住

三、如何实现监测死锁的组件

很简单,根据死锁的成因:

我们可以构建由tid(线程id)节点构成的有向图,当其成环时表明死锁已经形成。

实现步骤:

  1. 构建 mutex - tid 对应表
  2. tid争夺锁之前,根据对应表查找是否有other_tid持有该锁。
    有的话,则构建边 tid -> other_tid
  3. 争夺成功锁之后,删除边 tid -> other_id (如果有的话),同时往对应表中插入(mutex, tid)
  4. 释放锁之后,删除对应表中的项(mutex, tid)
  5. 可以另开一个线程,定期监测有向图是否成环

四、代码(C++)

cpp 复制代码
#include <mutex>
#include <unordered_map>
#include <unordered_set>
#include <thread>
#include <chrono>
#include <iostream>
#include <queue>
#include <functional>

// 线程ID类型(哈希std::thread::id得到)
using tid_t = std::size_t;

// 共享状态:需要全局锁保护(多线程操作)
std::mutex graph_protect_mutex;               // 保护下面两个结构的互斥锁
std::unordered_map<const void*, tid_t> mutex_tid_map; // mutex地址 → 持有它的线程ID
std::unordered_map<tid_t, std::unordered_set<tid_t>> wait_graph; // 等待图:tid → 它等待的tid集合


// 获取当前线程的ID(哈希std::thread::id)
tid_t get_current_tid() {
    std::hash<std::thread::id> tid_hasher;
    return tid_hasher(std::this_thread::get_id());
}


// 检测等待图是否有环(死锁):拓扑排序实现
bool detect_deadlock() {
    // 先复制等待图(减少锁持有时间)
    std::unordered_map<tid_t, std::unordered_set<tid_t>> graph_copy;
    {
        std::lock_guard<std::mutex> lock(graph_protect_mutex);
        graph_copy = wait_graph;
    }

    // 1. 计算每个节点的入度
    std::unordered_map<tid_t, int> in_degree;
    for (const auto& [from_tid, to_tids] : graph_copy) {
        in_degree.try_emplace(from_tid, 0); // 初始化入度为0(若不存在)
        for (tid_t to_tid : to_tids) {
            in_degree[to_tid]++;
        }
    }

    // 2. 拓扑排序:初始化入度为0的节点队列
    std::queue<tid_t> zero_in_degree;
    for (const auto& [node, degree] : in_degree) {
        if (degree == 0) zero_in_degree.push(node);
    }

    // 3. 处理节点,统计已处理数量
    int processed_count = 0;
    while (!zero_in_degree.empty()) {
        tid_t curr = zero_in_degree.front();
        zero_in_degree.pop();
        processed_count++;

        // 减少邻居的入度
        if (graph_copy.count(curr)) {
            for (tid_t neighbor : graph_copy.at(curr)) {
                if (--in_degree[neighbor] == 0) {
                    zero_in_degree.push(neighbor);
                }
            }
        }
    }

    // 若处理的节点数 < 总节点数 → 存在环(死锁)
    return processed_count < in_degree.size();
}


// 死锁监测线程:定期检测等待图
void deadlock_monitor() {
    while (true) {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        if (detect_deadlock()) {
            std::cerr << "\n[死锁警告] 检测到循环等待!等待图如下:\n";
            // 加锁输出等待图详情
            std::lock_guard<std::mutex> lock(graph_protect_mutex);
            for (const auto& [waiter, holders] : wait_graph) {
                std::cerr << "  线程" << waiter << " 等待 → ";
                for (tid_t holder : holders) std::cerr << holder << " ";
                std::cerr << "\n";
            }
        }
    }
}


// 带死锁监测的互斥锁类
class MonitoredMutex {
private:
    std::mutex internal_mutex; // 实际的互斥锁

public:
    void lock() {
        tid_t curr_tid = get_current_tid();
        const void* mutex_addr = static_cast<const void*>(this);

        // 步骤2:抢锁前→检查是否被其他线程持有,添加等待边
        {
            std::lock_guard<std::mutex> lock(graph_protect_mutex);
            auto it = mutex_tid_map.find(mutex_addr);
            if (it != mutex_tid_map.end()) {
                tid_t holder_tid = it->second;
                wait_graph[curr_tid].insert(holder_tid); // 记录"curr_tid等待holder_tid"
            }
        }

        // 实际抢锁(阻塞直到成功)
        internal_mutex.lock();

        // 步骤3:抢锁成功→删除等待边,更新mutex-tid表
        {
            std::lock_guard<std::mutex> lock(graph_protect_mutex);
            auto it = mutex_tid_map.find(mutex_addr);
            if (it != mutex_tid_map.end()) {
                tid_t holder_tid = it->second;
                // 删除"curr_tid→holder_tid"的边
                auto& neighbors = wait_graph[curr_tid];
                neighbors.erase(holder_tid);
                if (neighbors.empty()) wait_graph.erase(curr_tid);
            }
            // 记录"当前mutex被curr_tid持有"
            mutex_tid_map[mutex_addr] = curr_tid;
        }
    }

    void unlock() {
        const void* mutex_addr = static_cast<const void*>(this);
        tid_t curr_tid = get_current_tid();

        // 步骤4:释放锁→从mutex-tid表中移除
        {
            std::lock_guard<std::mutex> lock(graph_protect_mutex);
            auto it = mutex_tid_map.find(mutex_addr);
            if (it != mutex_tid_map.end() && it->second == curr_tid) {
                mutex_tid_map.erase(it);
            }
        }

        // 实际释放锁
        internal_mutex.unlock();
    }

    // 支持std::lock_guard等RAII工具
    bool try_lock() { return internal_mutex.try_lock(); }
};


// 测试:模拟死锁场景
int main() {
    // 启动死锁监测线程(后台运行)
    std::thread monitor_thread(deadlock_monitor);
    monitor_thread.detach();

    MonitoredMutex m1, m2, m3, m4;


    std::lock_guard<MonitoredMutex> lock4(m4);

   
    std::thread t1([&]() {
        std::lock_guard<MonitoredMutex> lock1(m1);
        std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 放大死锁概率
        std::lock_guard<MonitoredMutex> lock2(m2);
        std::cout << "线程1执行完成\n";
    });

    std::thread t3([&]() {
        std::lock_guard<MonitoredMutex> lock3(m3);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        std::lock_guard<MonitoredMutex> lock1(m1);
        std::cout << "线程2执行完成\n";
    });


    
    std::thread t2([&]() {
        std::lock_guard<MonitoredMutex> lock2(m2);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        std::lock_guard<MonitoredMutex> lock3(m3);
        std::cout << "线程2执行完成\n";
    });


    t1.join();
    t2.join();
    t3.join();

    return 0;
}
相关推荐
电子小白1238 小时前
第13期PCB layout工程师初级培训-1-EDA软件的通用设置
笔记·嵌入式硬件·学习·pcb·layout
唯情于酒9 小时前
Docker学习
学习·docker·容器
charlie11451419110 小时前
嵌入式现代C++教程: 构造函数优化:初始化列表 vs 成员赋值
开发语言·c++·笔记·学习·嵌入式·现代c++
IT=>小脑虎11 小时前
C++零基础衔接进阶知识点【详解版】
开发语言·c++·学习
#眼镜&11 小时前
嵌入式学习之路2
学习
码农小韩11 小时前
基于Linux的C++学习——指针
linux·开发语言·c++·学习·算法
微露清风11 小时前
系统性学习C++-第十九讲-unordered_map 和 unordered_set 的使用
开发语言·c++·学习
wdfk_prog11 小时前
[Linux]学习笔记系列 -- [fs]seq_file
linux·笔记·学习
行业探路者12 小时前
二维码标签是什么?主要有线上生成二维码和文件生成二维码功能吗?
学习·音视频·语音识别·二维码·设备巡检
li星野12 小时前
OpenCV4X学习—核心模块Core
人工智能·opencv·学习