gdb调试死锁
Linux下gdb调试死锁
死锁条件
-
互斥条件(Mutual Exclusion)
定义:资源具有排他性,即一个资源在同一时间只能被一个线程(或进程)占用,其他线程必须等待该资源被释放后才能使用。解释:这是死锁产生的基础。如果资源可以被多个线程同时访问(如读写锁的 “读模式”),则不会因争夺该资源而产生死锁。
示例:打印机、独占锁(如std::mutex)都是互斥资源。当线程 A 持有mutex1时,线程 B 必须等待 A 释放mutex1才能获取。
-
持有并等待条件(Hold and Wait)
定义:一个线程已经持有至少一个资源,同时又在等待获取其他线程所持有的资源,且在等待过程中不释放自己已持有的资源。解释:线程在获取资源时并非一次性获取所有需要的资源,而是先持有部分资源,再等待其他资源,这就可能导致多个线程互相等待对方的资源。
示例:线程 A 持有mutex1,同时等待mutex2;线程 B 持有mutex2,同时等待mutex1。此时两者都 “持有部分资源并等待其他资源”,满足该条件。
-
不可剥夺条件(No Preemption)
定义:线程已持有的资源不能被其他线程强制剥夺,只能由该线程主动释放。解释:如果资源可以被强制剥夺(例如操作系统的进程调度中,高优先级进程可抢占低优先级进程的 CPU 资源),则死锁不会发生。但多数资源(如锁、文件句柄)不支持强制剥夺,因此该条件容易满足。
示例:线程 A 持有mutex1,线程 B 等待mutex1时,无法强制让 A 释放mutex1,只能等待 A 主动调用unlock()。
-
循环等待条件(Circular Wait)
定义:多个线程之间形成一种首尾相接的等待关系,即线程 1 等待线程 2 持有的资源,线程 2 等待线程 3 持有的资源,……,线程 n 等待线程 1 持有的资源,形成一个闭环。解释:这是死锁的直观表现。循环等待依赖于前三个条件的存在,只有当多个线程互相等待对方的资源时,才会形成闭环。
示例:线程 A 等待线程 B 的mutex2,线程 B 等待线程 A 的mutex1,形成 “A→B→A” 的循环等待;若有三个线程,则可能形成 “A→B→C→A” 的闭环。
代码
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>using namespace std;// 定义两个互斥锁
mutex mtx1, mtx2;// 线程1:先获取mtx1,再获取mtx2
void thread_func1() {cout << "线程1:尝试获取mtx1" << endl;lock_guard<mutex> lock1(mtx1); // 获取mtx1cout << "线程1:已获取mtx1" << endl;// 模拟处理时间,让线程2有机会获取mtx2this_thread::sleep_for(chrono::seconds(1));cout << "线程1:尝试获取mtx2" << endl;lock_guard<mutex> lock2(mtx2); // 尝试获取mtx2,此时会阻塞cout << "线程1:已获取mtx2(不会执行到这里)" << endl;
}// 线程2:先获取mtx2,再获取mtx1
void thread_func2() {cout << "线程2:尝试获取mtx2" << endl;lock_guard<mutex> lock2(mtx2); // 获取mtx2cout << "线程2:已获取mtx2" << endl;// 模拟处理时间,让线程1有机会获取mtx1this_thread::sleep_for(chrono::seconds(1));cout << "线程2:尝试获取mtx1" << endl;lock_guard<mutex> lock1(mtx1); // 尝试获取mtx1,此时会阻塞cout << "线程2:已获取mtx1(不会执行到这里)" << endl;
}int main() {thread t1(thread_func1);thread t2(thread_func2);t1.join();t2.join();cout << "程序结束(不会执行到这里)" << endl;return 0;
}
gbd调试过程
-
运行程序,程序因死锁无法继续执行
-
Ctrl+C停止,info threads查看所有线程信息
-
查看thread 2,bt查看堆栈,程序在main.cpp 124行卡住