C++高效死锁检测——实现原理与应用(基于强连通分量)

背景

在项目使用多进程、多线程过程中,因争夺资源而造成一种资源竞态,所以需加锁处理。如下图所示,线程 A 想获取线程 B 的锁,线程 B 想获取线程 C 的锁,线程 C 想获取线程 D 的锁, 线程 D 想获取线程 A 的锁,从而构建了一个资源获取环,当进程或者线程申请的锁处于相互交叉锁住的情况,就会出现死锁,它们将无法继续运行。

死锁的存在是因为有资源获取环的存在,所以只要能检测出资源获取环,就等同于检测出死锁的存在。

设计方案

本文实现的是一个锁管理器,提供加锁解锁功能,同时提供检测死锁功能,出现死锁后释放部分资源来解决死锁。死锁的检测是通过检测死锁图中有没有环来实现的,如果对于请求同一资源的两个锁L1和L2(其对应的进程为P1和P2),L1已经获得资源而L2在等待,则死锁图中有一条边P2->P1。

有向图中环的检测,即找到图中所有的强连通分量,使用Tarjan算法来实现,可以在O(E+V)时间找到所有的环。死锁图一般是比较稀疏的图,存储使用邻接表。

锁的数据结构为:

cpp 复制代码
class Lock {
public:
    Lock(int p, int res, int stat) {
        pid = p;
        res_id = res;
        state = stat;
    }
    int pid;
    int res_id;
    int state; //0 == locked, 1 == waiting
};

锁的状态有两种,已持有,等待。对于同一个资源加的锁放在链表中,方便检索和随机位置的删除。如果一个锁L1是资源R1对应链表的头,则他是一个已经持有的锁,链表其他位置的锁Ln都在阻塞等待L1释放,因此在死锁图中新建 Ln.pid -> L1.pid 边。

锁管理器的类声明如下,实现了后台线程进行死锁检测。

cpp 复制代码
class LockManager {
public:
    static LockManager& getInstance();
    ~LockManager();
    // 获取锁,返回一个指向Lock对象的shared_ptr,ret表示结果
    shared_ptr<Lock> getLock(int pid, int res_id, int& ret);
    // 查找指定pid和res_id的锁,返回一个指向Lock对象的shared_ptr
    shared_ptr<Lock> findLock(int pid, int res_id);
    // 释放锁
    int releaseLock(shared_ptr<Lock> lock);
    // 检测是否有死锁,若有,设置tokill为需要解除锁的pid
    bool isDeadLock(int& pid);
    void print();
    // 启动死锁检测器,interval为检测间隔
    void startDetection(int interval);
    void stopDeadlockDetector();

private:
    LockManager();
    // 计算强连通分量(SCC),存储在pid_to_SCCid中
    void calSCC(map<int, vector<int>>& pid_to_SCCid);
    // 释放指定pid的所有锁
    void releaseProcess(int pid); 
    // 内部获取锁的实现,返回一个指向Lock对象的shared_ptr,possible表示是否可能发生死锁
    shared_ptr<Lock> getLockInternal(int pid, int res_id, bool& possible);
    // 内部释放锁的实现,返回操作结果,possible表示是否可能发生死锁
    int releaseLockInternal(shared_ptr<Lock> lock, bool& possible);
    void detectDeadlock();

    map<int, list<shared_ptr<Lock>>> res_to_locklist;// 资源ID到锁列表的映射
    map<int, pair<int, list<shared_ptr<Lock>>>> pid_to_locks;// 进程ID到锁计数和锁列表的映射
    map<int, list<int>> lock_graph;// 锁图,表示进程之间的等待依赖关系
    set<int> pid_set;// 进程ID集合,用于跟踪所有活跃的进程ID
    // 用于后台线程检测死锁的参数
    thread* deadlock_checker;// 指向死锁检测器线程的指针
    bool stop;
    int check_interval;
    mutex mtx;
};

具体实现

创建锁节点

获取锁:当一个进程 (pid) 请求一个资源 (res_id) 的锁时,会调用 getLock 方法。该方法首先检查该进程是否已经拥有该资源的锁(通过 findLock 方法)。

cpp 复制代码
shared_ptr<Lock> LockManager::getLock(int pid, int res_id, int& ret) {
    bool deadlock_possible;
    ret = 0;
    if (findLock(pid, res_id) != nullptr) {
        ret = 1; 
        return nullptr;
    }
    mtx.lock();
    auto p = getLockInternal(pid, res_id, deadlock_possible);
    mtx.unlock();
    return p;
}

内部获取锁逻辑:

  • 如果没有找到重复的锁,会调用内部方法 getLockInternal 来实际创建并获取锁。
  • 在 getLockInternal 方法中,会根据资源是否已经被其他进程锁定来创建不同状态的锁对象:
  • 如果资源没有被锁定,创建一个状态为 0 的锁对象(表示已锁定)。
  • 如果资源已经被其他进程锁定,创建一个状态为 1 的锁对象(表示等待)。
cpp 复制代码
shared_ptr<Lock> LockManager::getLockInternal(int pid, int res_id, bool& deadlock_possible) {
    deadlock_possible = false; // 初始无死锁
    if (!res_to_locklist.count(res_id)) res_to_locklist[res_id] = list<shared_ptr<Lock>>{};
    if (!pid_to_locks.count(pid)) pid_to_locks[pid] = make_pair(0, list<shared_ptr<Lock>>{});
    pid_set.insert(pid); // 加入线程

    shared_ptr<Lock> newlock;
    if (res_to_locklist[res_id].size() == 0) {
        newlock = make_shared<Lock>(pid, res_id, 0);
        res_to_locklist[res_id].push_back(newlock);
        pid_to_locks[pid].first++;
        pid_to_locks[pid].second.push_back(newlock);
    }
    else {
        newlock = make_shared<Lock>(pid, res_id, 1);
        res_to_locklist[res_id].push_back(newlock);
        pid_to_locks[pid].second.push_back(newlock);
        if (pid_to_locks[pid].first > 0) {
            const auto& first_lock = res_to_locklist[res_id].front();
            int p0id = first_lock->pid;
            if (!lock_graph.count(pid)) lock_graph[pid] = list<int>{};
            lock_graph[pid].push_back(p0id);

            deadlock_possible = true; // 可能发生死锁需要检查
        }
    }
    return newlock;
}

释放锁

调用 releaseLock 方法释放锁对象。releaseLock 方法调用 releaseLockInternal 方法,实际进行锁释放操作。

内部释放锁的逻辑:

  • 从 res_to_locklist 中移除该锁对象。
  • 从 pid_to_locks 中移除该锁对象。
  • 如果锁对象的状态是 0(已锁定)且资源上仍有其他锁,则将资源上的下一个等待锁(状态为 1)转换为已锁定状态(状态为 0),并更新相应进程的锁计数。
  • 更新依赖关系图 (lock_graph):
  • 移除当前进程到所有依赖于它的进程的边。
  • 添加新的依赖关系,即新的持有锁的进程到其他等待进程的边。
cpp 复制代码
int LockManager::releaseLock(shared_ptr<Lock> lock) {
    bool deadlock_possible;
    mtx.lock();
    releaseLockInternal(lock, deadlock_possible);
    mtx.unlock();
    return 0;
}
int LockManager::releaseLockInternal(shared_ptr<Lock> lock, bool& deadlock_possible) {
    deadlock_possible = false; // 初始化为没有死锁的可能性
    int pid = lock->pid; // 获取锁的进程ID
    int res_id = lock->res_id; // 获取锁的资源ID

    auto& locklist = res_to_locklist[res_id]; // 获取资源对应的锁列表
    locklist.remove(lock); // 从资源的锁列表中移除该锁
    pid_to_locks[pid].second.remove(lock); // 从进程的锁列表中移除该锁

    printf("release lock(pid=%d, res_id=%d, state=%d)\n", pid, res_id, lock->state);
    if (lock->state == 0 && locklist.size() > 0) { // 如果释放的是已锁定状态的锁,且资源上还有其他等待的锁
        pid_to_locks[pid].first--; // 减少该进程的锁计数
        int p0id = locklist.front()->pid; // 获取新获得锁的进程ID
        locklist.front()->state = 0; // 将等待的锁状态改为已锁定
        pid_to_locks[p0id].first++; // 增加新获得锁的进程的锁计数

        for (auto it = locklist.begin(); it != locklist.end(); it++) { // 更新依赖关系图
            int p1id = (*it)->pid;
            lock_graph[p1id].remove(pid); // 移除指向释放锁的进程的依赖关系
            printf("remove edge(%d->%d)\n", p1id, pid);
            if (p1id != p0id) {
                lock_graph[p1id].push_back(p0id); // 添加新的依赖关系,指向新获得锁的进程
                printf("add edge(%d->%d)\n", p1id, p0id);
            }
        }
        // 可能导致死锁,需要检查
        deadlock_possible = true;
    }

    return 0;
}

Kosaraju算法

对反向图进行拓扑排序,并按照拓扑排序的逆序进行深度优先搜索 (DFS),是为了高效地找到原始图中的强连通分量 (SCC)。这种方法称为 Kosaraju算法,其主要思想是:

拓扑排序 确定访问顺序:

  • 对反向图进行拓扑排序,可以得到一个访问顺序,使得在原图中从某个节点出发的所有可能路径都被访问到。
  • 拓扑排序保证了在原图中,某个节点的所有后继节点在排序中都在它之前。这有助于后续步骤中的 SCC 检测。

逆序DFS 高效找到 SCC:

  • 按照拓扑排序的逆序进行 DFS 确保每次从尚未访问的节点出发时,能够遍历一个完整的强连通分量。
  • 由于拓扑排序的逆序保证了我们从图的"后面"开始访问(即从没有后继节点的节点开始),所以每次 DFS 都会完全包含一个 SCC。

这种方法的效率很高,因为每个节点和每条边都只被访问两次(一次在拓扑排序时,一次在逆序 DFS 时),所以 Kosaraju 算法的时间复杂度是 O(V + E),其中 V 是节点数,E 是边数。

死锁检测是通过计算锁图的强连通分量 (SCC) 来实现的。首先通过 reverseGraph 方法构建锁图的反向图。

cpp 复制代码
void reverseGraph(map<int, list<int>>& origin, map<int, list<int>>& dest) {
    for (const auto& p : origin) {
        int e = p.first;
        const auto& vec = p.second;
        for (auto v : vec) {
            if (!dest.count(v)) dest[v] = list<int>{};
            dest[v].push_back(e);
        }
    }
}

通过 topoSort 方法对反向图进行拓扑排序,按照拓扑排序的逆序进行DFS。

cpp 复制代码
void dfs(map<int, list<int>>& graph, int cur, set<int>& visited, vector<int>& topo_order) {
    if (visited.count(cur)) return;
    visited.insert(cur);
    for (auto x : graph[cur]) {
        dfs(graph, x, visited, topo_order);
    }
    topo_order.push_back(cur);
}

void topoSort(map<int, list<int>>& graph, set<int>& pid_set, vector<int>& topo_order) {
    set<int> visited;
    for (auto x : pid_set) {
        dfs(graph, x, visited, topo_order);
    }
}

通过深度优先搜索 (DFS) 来计算锁图中的强连通分量。检查是否存在强连通分量。如果存在,说明有死锁,并选择一个进程进行终止以打破死锁。

cpp 复制代码
void LockManager::calSCC(map<int, vector<int>>& pid_to_SCCid) {
    map<int, list<int>> reverse_graph;
    reverseGraph(lock_graph, reverse_graph); // 构建反向图
    vector<int> topo_order;
    topoSort(reverse_graph, pid_set, topo_order); // 对反向图进行拓扑排序

    set<int> visited;
    for (int i = topo_order.size() - 1; i >= 0; i--) { // 按照拓扑排序的逆序进行DFS
        auto vec = vector<int>{};
        dfs(lock_graph, topo_order[i], visited, vec);
        if (vec.size() > 1) pid_to_SCCid[i] = vec; // 找到强连通分量
    }
}

后台检测死锁

isDeadLock是实际进行检测死锁的函数:

计算强连通分量 (SCC):调用 calSCC 方法,计算图中的强连通分量并将结果存储。每个强连通分量表示一个可能的死锁环。

遍历每个强连通分量:对于每个强连通分量中的进程,打印其进程ID,并找到锁数量最少的进程。

选择要终止的进程:如果找到了锁数量最少的进程,将其标记为 tokill。通过终止该进程来打破死锁。

如果存在强连通分量,返回 true 表示存在死锁

cpp 复制代码
bool LockManager::isDeadLock(int& tokill) {
    map<int, vector<int>> SCCid_to_pids;
    calSCC(SCCid_to_pids); // 计算强连通分量
    for (const auto& cyc : SCCid_to_pids) { // 遍历每个强连通分量
        const auto& vec = cyc.second;
        printf("detected deadlock: ");
        int minlocks = 1e8;
        int minpid = -1;
        for (auto it = vec.begin(); it != vec.end(); it++) { // 打印并找到最少锁的进程
            printf("%d->", *it);
            int nlock = pid_to_locks[*it].first;
            if (nlock < minlocks) {
                minlocks = nlock;
                minpid = *it;
            }
        }
        printf("%d\n", vec.front());
        if (minpid != -1) {
            printf("will release pid=%d(%d) to break deadlock\n", minpid, minlocks);
            tokill = minpid; // 选择需要终止的进程
        }
    }
    return SCCid_to_pids.size() > 0; // 如果存在强连通分量,说明存在死锁
}

detectDeadlock方法是一个后台线程,用于定期检测死锁并处理死锁。

cpp 复制代码
LockManager::LockManager() {
    stop = false;
    check_interval = 1;
    deadlock_checker = new thread([this] { this->detectDeadlock(); });
}
void LockManager::detectDeadlock() {
    using std::chrono::system_clock;
    while (!stop) {
        int tokill;
        mtx.lock();
        while (isDeadLock(tokill)) {
            releaseProcess(tokill);
        }
        mtx.unlock();
        // 检查死锁
        std::time_t tt = system_clock::to_time_t(system_clock::now());
        struct std::tm* ptm = std::localtime(&tt);
        ptm->tm_sec += check_interval;
        std::this_thread::sleep_until(system_clock::from_time_t(mktime(ptm)));
    }
}

结果展示

使用下文的完整代码测试,set lock设置锁,release lock释放,

cpp 复制代码
int main() {
    doGetLock(1, 2);
    doGetLock(1, 3);
    doGetLock(2, 2);
    doGetLock(3, 3);
    doGetLock(2, 3);
    doGetLock(3, 2);
    doReleaseLock(1, 2);
    doReleaseLock(1, 3);
    std::this_thread::sleep_for(std::chrono::seconds(2));
    doGetLock(5, 5);
    doGetLock(6, 6);
    doGetLock(5, 6);
    doGetLock(6, 5);
    int pid, resid;
    char tmp[40];
    while (scanf("%s %d %d", tmp, &pid, &resid) != EOF) {
        if (string(tmp) == "lock") {
            doGetLock(pid, resid);
        }
        else {
            doReleaseLock(pid, resid);
        }
    }
    std::this_thread::sleep_for(std::chrono::seconds(1));
    LockManager::getInstance().stopDeadlockDetector();
    return 0;
}
cpp 复制代码
set lock(pid=1, res_id=2)
set lock(pid=1, res_id=3)
set lock(pid=2, res_id=2)
set lock(pid=3, res_id=3)
set lock(pid=2, res_id=3)
set lock(pid=3, res_id=2)
release lock(pid=1, res_id=2, state=0)
remove edge(2->1)
remove edge(3->1)
add edge(3->2) //锁交还2
release lock(pid=1, res_id=3, state=0)
remove edge(3->1)
remove edge(2->1)
add edge(2->3)
detected deadlock: 3->2->3
will release pid=3(1) to break deadlock
release lock(pid=3, res_id=3, state=0)
remove edge(2->3)
release lock(pid=3, res_id=2, state=1)
erase pid 3
set lock(pid=5, res_id=5)
set lock(pid=6, res_id=6)
set lock(pid=5, res_id=6)
set lock(pid=6, res_id=5)
detected deadlock: 6->5->6
will release pid=6(1) to break deadlock
release lock(pid=6, res_id=6, state=0)
remove edge(5->6)
release lock(pid=6, res_id=5, state=1)
erase pid 6

设置锁:

  • set lock(pid=1, res_id=2): 进程1请求资源2的锁。
  • set lock(pid=1, res_id=3): 进程1请求资源3的锁。
  • set lock(pid=2, res_id=2): 进程2请求资源2的锁。
  • set lock(pid=3, res_id=3): 进程3请求资源3的锁。
  • set lock(pid=2, res_id=3): 进程2请求资源3的锁。
  • set lock(pid=3, res_id=2): 进程3请求资源2的锁。

释放锁:

  • release lock(pid=1, res_id=2, state=0): 进程1释放资源2的锁(锁处于已锁定状态)。
  • remove edge(2->1): 从进程2到进程1的依赖关系被移除。
  • remove edge(3->1): 从进程3到进程1的依赖关系被移除。
  • add edge(3->2): 添加新的依赖关系,从进程3到进程2。
  • release lock(pid=1, res_id=3, state=0): 进程1释放资源3的锁(锁处于已锁定状态)。
  • remove edge(3->1): 从进程3到进程1的依赖关系被移除。
  • remove edge(2->1): 从进程2到进程1的依赖关系被移除。
  • add edge(2->3): 添加新的依赖关系,从进程2到进程3。

检测到死锁:

  • detected deadlock: 3->2->3: 检测到由进程3和进程2组成的死锁循环。
  • will release pid=3(1) to break deadlock: 选择进程3(持有一个锁)来解除死锁。

解除死锁:

  • release lock(pid=3, res_id=3, state=0): 释放进程3持有的资源3的锁(锁处于已锁定状态)。
  • remove edge(2->3): 移除从进程2到进程3的依赖关系。
  • release lock(pid=3, res_id=2, state=1): 释放进程3持有的资源2的锁(锁处于等待状态)。
  • erase pid 3: 从锁管理器中移除进程3。

设置新的锁:

  • set lock(pid=5, res_id=5): 进程5请求资源5的锁。
  • set lock(pid=6, res_id=6): 进程6请求资源6的锁。
  • set lock(pid=5, res_id=6): 进程5请求资源6的锁。
  • set lock(pid=6, res_id=5): 进程6请求资源5的锁。

再次检测到死锁:

  • detected deadlock: 6->5->6: 检测到由进程6和进程5组成的死锁循环。
  • will release pid=6(1) to break deadlock: 选择进程6(持有一个锁)来解除死锁。

解除新的死锁:

  • release lock(pid=6, res_id=6, state=0): 释放进程6持有的资源6的锁(锁处于已锁定状态)。
  • remove edge(5->6): 移除从进程5到进程6的依赖关系。
  • release lock(pid=6, res_id=5, state=1): 释放进程6持有的资源5的锁(锁处于等待状态)。
  • erase pid 6: 从锁管理器中移除进程6。

完整代码

cpp 复制代码
#include <iostream>
#include <memory>
#include <map>
#include <list>
#include <vector>
#include <set>
#include <string>
#include <thread>
#include <chrono>
#include <mutex>
using namespace std;

class Lock {
public:
    Lock(int p, int res, int stat) {
        pid = p;
        res_id = res;
        state = stat;
    }
    int pid;
    int res_id;
    int state; //0 == locked, 1 == waiting
};

typedef pair<int, list<shared_ptr<Lock> > > pivec;


class LockManager {
public:
    static LockManager& getInstance();
    ~LockManager();
    // 获取锁,返回一个指向Lock对象的shared_ptr,ret表示结果
    shared_ptr<Lock> getLock(int pid, int res_id, int& ret);
    // 查找指定pid和res_id的锁,返回一个指向Lock对象的shared_ptr
    shared_ptr<Lock> findLock(int pid, int res_id);
    // 释放锁
    int releaseLock(shared_ptr<Lock> lock);
    // 检测是否有死锁,若有,设置tokill为需要解除锁的pid
    bool isDeadLock(int& pid);
    void print();
    // 启动死锁检测器,interval为检测间隔
    void startDetection(int interval);
    void stopDeadlockDetector();

private:
    LockManager();
    // 计算强连通分量(SCC),存储在pid_to_SCCid中
    void calSCC(map<int, vector<int>>& pid_to_SCCid);
    // 释放指定pid的所有锁
    void releaseProcess(int pid); 
    // 内部获取锁的实现,返回一个指向Lock对象的shared_ptr,possible表示是否可能发生死锁
    shared_ptr<Lock> getLockInternal(int pid, int res_id, bool& possible);
    // 内部释放锁的实现,返回操作结果,possible表示是否可能发生死锁
    int releaseLockInternal(shared_ptr<Lock> lock, bool& possible);
    void detectDeadlock();

    map<int, list<shared_ptr<Lock>>> res_to_locklist;// 资源ID到锁列表的映射
    map<int, pair<int, list<shared_ptr<Lock>>>> pid_to_locks;// 进程ID到锁计数和锁列表的映射
    map<int, list<int>> lock_graph;// 锁图,表示进程之间的等待依赖关系
    set<int> pid_set;// 进程ID集合,用于跟踪所有活跃的进程ID
    // 用于后台线程检测死锁的参数
    thread* deadlock_checker;// 指向死锁检测器线程的指针
    bool stop;
    int check_interval;
    mutex mtx;
};

LockManager::LockManager() {
    stop = false;
    check_interval = 1;
    deadlock_checker = new thread([this] { this->detectDeadlock(); });
}

LockManager::~LockManager() {
    stopDeadlockDetector();
}

LockManager& LockManager::getInstance() {
    static LockManager inst;
    return inst;
}

shared_ptr<Lock> LockManager::getLock(int pid, int res_id, int& ret) {
    bool deadlock_possible;
    ret = 0;
    if (findLock(pid, res_id) != nullptr) {
        ret = 1; 
        return nullptr;
    }
    mtx.lock();
    auto p = getLockInternal(pid, res_id, deadlock_possible);
    mtx.unlock();
    return p;
}

shared_ptr<Lock> LockManager::getLockInternal(int pid, int res_id, bool& deadlock_possible) {
    deadlock_possible = false; 
    if (!res_to_locklist.count(res_id)) res_to_locklist[res_id] = list<shared_ptr<Lock>>{};
    if (!pid_to_locks.count(pid)) pid_to_locks[pid] = make_pair(0, list<shared_ptr<Lock>>{});
    pid_set.insert(pid);

    shared_ptr<Lock> newlock;
    if (res_to_locklist[res_id].size() == 0) {
        newlock = make_shared<Lock>(pid, res_id, 0);
        res_to_locklist[res_id].push_back(newlock);
        pid_to_locks[pid].first++;
        pid_to_locks[pid].second.push_back(newlock);
    }
    else {
        newlock = make_shared<Lock>(pid, res_id, 1);
        res_to_locklist[res_id].push_back(newlock);
        pid_to_locks[pid].second.push_back(newlock);
        if (pid_to_locks[pid].first > 0) {
            const auto& first_lock = res_to_locklist[res_id].front();
            int p0id = first_lock->pid;
            if (!lock_graph.count(pid)) lock_graph[pid] = list<int>{};
            lock_graph[pid].push_back(p0id);

            deadlock_possible = true;
        }
    }
    return newlock;
}

shared_ptr<Lock> LockManager::findLock(int pid, int res_id) {
    shared_ptr<Lock> result = nullptr;
    mtx.lock();
    for (const auto& p : pid_to_locks[pid].second) {
        if (p->res_id == res_id) result = p;
    }
    mtx.unlock();
    return result;
}

int LockManager::releaseLock(shared_ptr<Lock> lock) {
    bool deadlock_possible;
    mtx.lock();
    releaseLockInternal(lock, deadlock_possible);
    mtx.unlock();
    return 0;
}

int LockManager::releaseLockInternal(shared_ptr<Lock> lock, bool& deadlock_possible) {
    deadlock_possible = false;
    int pid = lock->pid;
    int res_id = lock->res_id;

    auto& locklist = res_to_locklist[res_id];
    locklist.remove(lock);
    pid_to_locks[pid].second.remove(lock);

    printf("release lock(pid=%d, res_id=%d, state=%d)\n", pid, res_id, lock->state);
    if (lock->state == 0 && locklist.size() > 0) {
        pid_to_locks[pid].first--;
        int p0id = locklist.front()->pid;
        locklist.front()->state = 0;
        pid_to_locks[p0id].first++;

        for (auto it = locklist.begin(); it != locklist.end(); it++) {
            int p1id = (*it)->pid;
            lock_graph[p1id].remove(pid); 
            printf("remove edge(%d->%d)\n", p1id, pid);
            if (p1id != p0id) {
                lock_graph[p1id].push_back(p0id);
                printf("add edge(%d->%d)\n", p1id, p0id);
            }
        }
        deadlock_possible = true;
    }

    return 0;
}

bool LockManager::isDeadLock(int& tokill) {
    map<int, vector<int>> SCCid_to_pids;
    calSCC(SCCid_to_pids);
    for (const auto& cyc : SCCid_to_pids) {
        const auto& vec = cyc.second;
        printf("detected deadlock: ");
        int minlocks = 1e8;
        int minpid = -1;
        for (auto it = vec.begin(); it != vec.end(); it++) {
            printf("%d->", *it);
            int nlock = pid_to_locks[*it].first;
            if (nlock < minlocks) {
                minlocks = nlock;
                minpid = *it;
            }
        }
        printf("%d\n", vec.front());
        if (minpid != -1) {
            printf("will release pid=%d(%d) to break deadlock\n", minpid, minlocks);
            tokill = minpid;
        }
    }
    return SCCid_to_pids.size() > 0;
}

void LockManager::releaseProcess(int pid) {
    if (pid_to_locks.count(pid)) {
        list<shared_ptr<Lock>> tmplist = pid_to_locks[pid].second;
        for (const auto& p_lock : tmplist) {
            bool possible; 
            releaseLockInternal(p_lock, possible);
        }
        pid_to_locks.erase(pid);
    };
    if (lock_graph.count(pid)) lock_graph.erase(pid);
    pid_set.erase(pid);
    printf("erase pid %d\n", pid);
}

//死锁检测

void LockManager::startDetection(int interval) {
    if (deadlock_checker != nullptr) {
        check_interval = interval;
        deadlock_checker = new thread([this] {
            this->detectDeadlock();
            });
    }
}

void LockManager::stopDeadlockDetector() {
    stop = true;
    if (deadlock_checker && deadlock_checker->joinable()) {
        printf("deadlock detector is stoped\n");
        deadlock_checker->join();
        deadlock_checker = nullptr;
    }
}

void LockManager::detectDeadlock() {
    using std::chrono::system_clock;
    while (!stop) {
        int tokill;
        mtx.lock();
        while (isDeadLock(tokill)) {
            releaseProcess(tokill);
        }
        mtx.unlock();

        // 检查死锁
        std::time_t tt = system_clock::to_time_t(system_clock::now());
        struct std::tm* ptm = std::localtime(&tt);
        ptm->tm_sec += check_interval;
        std::this_thread::sleep_until(system_clock::from_time_t(mktime(ptm)));
    }
}


// 计算SCC的辅助函数

void reverseGraph(map<int, list<int>>& origin, map<int, list<int>>& dest) {
    for (const auto& p : origin) {
        int e = p.first;
        const auto& vec = p.second;
        for (auto v : vec) {
            if (!dest.count(v)) dest[v] = list<int>{};
            dest[v].push_back(e);
        }
    }
}

void dfs(map<int, list<int>>& graph, int cur, set<int>& visited, vector<int>& topo_order) {
    if (visited.count(cur)) return;
    visited.insert(cur);
    for (auto x : graph[cur]) {
        dfs(graph, x, visited, topo_order);
    }
    topo_order.push_back(cur);
}

void topoSort(map<int, list<int>>& graph, set<int>& pid_set, vector<int>& topo_order) {
    set<int> visited;
    for (auto x : pid_set) {
        dfs(graph, x, visited, topo_order);
    }
}

void printVec(vector<int>& vec) {
    printf("vector[%d", vec.front());
    for (auto it = vec.begin() + 1; it != vec.end(); it++) {
        printf(",%d", *it);
    }
    printf("]\n");
}

void LockManager::calSCC(map<int, vector<int>>& pid_to_SCCid) {
    map<int, list<int>> reverse_graph;
    reverseGraph(lock_graph, reverse_graph);
    vector<int> topo_order;
    topoSort(reverse_graph, pid_set, topo_order);
    set<int> visited;
    for (int i = topo_order.size() - 1; i >= 0; i--) {
        auto vec = vector<int>{};
        dfs(lock_graph, topo_order[i], visited, vec);
        if (vec.size() > 1) pid_to_SCCid[i] = vec;
    }
}

void LockManager::print() {
    cout << "pid to locks:[\n";
    for (const auto& p : pid_to_locks) {

        printf("(%d, %d, [", p.first, p.second.first);
        for (const auto& q : p.second.second) {
            printf("(pid=%d, res_id=%d, state=%d),", q->pid, q->res_id, q->state);
        }
        printf("])\n");
    }
    cout << ']' << endl << "res to locks:[\n";
    for (const auto& p : res_to_locklist) {
        printf("(%d, [", p.first);
        for (const auto& q : p.second) {
            printf("(pid=%d, res_id=%d, state=%d),", q->pid, q->res_id, q->state);
        }
        printf("])\n");
    }
    cout << ']' << endl << "graph:[\n";
    for (const auto& p : lock_graph) {
        int e = p.first;
        printf("%d:[", e);
        for (const auto& q : p.second) {
            printf("%d,", q);
        }
        printf("]\n");
    }
    cout << ']' << endl << "pid_set:[";
    for (auto x : pid_set) printf("%d, ", x);
    cout << ']' << endl;
}

int doGetLock(int pid, int rid) {
    static auto& lock_manager = LockManager::getInstance();
    int ret;
    printf("set lock(pid=%d, res_id=%d)\n", pid, rid);
    lock_manager.getLock(pid, rid, ret);
    if (ret == 1) {
        printf("lock(pid=%d, res_id=%d) is duplicated, lock failed\n", pid, rid);
    }
    return ret;
}

int doReleaseLock(int pid, int rid) {
    static auto& lock_manager = LockManager::getInstance();
    auto lock = lock_manager.findLock(pid, rid);
    if (lock != nullptr) lock_manager.releaseLock(lock);
    else printf("no such lock(pid=%d, res_id=%d)\n", pid, rid);
    return 0;
}

int main() {
    doGetLock(1, 2);
    doGetLock(1, 3);
    doGetLock(2, 2);
    doGetLock(3, 3);
    doGetLock(2, 3);
    doGetLock(3, 2);
    doReleaseLock(1, 2);
    doReleaseLock(1, 3);

    std::this_thread::sleep_for(std::chrono::seconds(2));
    doGetLock(5, 5);
    doGetLock(6, 6);
    doGetLock(5, 6);
    doGetLock(6, 5);

    int pid, resid;
    char tmp[40];
    while (scanf("%s %d %d", tmp, &pid, &resid) != EOF) {
        if (string(tmp) == "lock") {
            doGetLock(pid, resid);
        }
        else {
            doReleaseLock(pid, resid);
        }
    }

    std::this_thread::sleep_for(std::chrono::seconds(1));

    LockManager::getInstance().stopDeadlockDetector();

    return 0;
}
相关推荐
武子康12 分钟前
大数据-258 离线数仓 - Griffin架构 配置安装 Livy 架构设计 解压配置 Hadoop Hive
java·大数据·数据仓库·hive·hadoop·架构
若亦_Royi14 分钟前
C++ 的大括号的用法合集
开发语言·c++
资源补给站1 小时前
大恒相机开发(2)—Python软触发调用采集图像
开发语言·python·数码相机
豪宇刘1 小时前
MyBatis的面试题以及详细解答二
java·servlet·tomcat
秋恬意1 小时前
Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别
java·数据库·mybatis
m0_748247551 小时前
Web 应用项目开发全流程解析与实战经验分享
开发语言·前端·php
6.942 小时前
Scala学习记录 递归调用 练习
开发语言·学习·scala
FF在路上2 小时前
Knife4j调试实体类传参扁平化模式修改:default-flat-param-object: true
java·开发语言
真的很上进2 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
众拾达人3 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言