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;
}
相关推荐
西岸行者2 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意2 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码2 天前
嵌入式学习路线
学习
毛小茛2 天前
计算机系统概论——校验码
学习
babe小鑫2 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms2 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下2 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。2 天前
2026.2.25监控学习
学习
im_AMBER2 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J2 天前
从“Hello World“ 开始 C++
c语言·c++·学习