【学习笔记】锁+死锁+gdb调试死锁
【学习笔记】锁+死锁+gdb调试死锁
一、互斥锁(std::mutex)
最基本的锁类型,提供排他性访问,同一时间仅允许一个线程持有锁。
#include <iostream>
#include <mutex>
#include <thread>std::mutex mtx; // 全局互斥锁
int shared_data = 0; // 共享资源void increment() {for (int i = 0; i < 100000; ++i) {std::lock_guard<std::mutex> lock(mtx); // 自动加锁++shared_data; // 临界区} // 作用域结束,自动解锁
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Final value: " << shared_data << std::endl; // 输出 200000return 0;
}
二、读写锁(std::shared_mutex)
多读单写锁,允许多个线程同时读,但写时独占。适用于读多写少的场景。
#include <shared_mutex> // C++17 及以上
#include <thread>
#include <vector>
#include <iostream>std::shared_mutex rw_mutex;
int shared_data = 0;// 读操作(允许多线程并发)
void reader(int id) {for (int i = 0; i < 1000; ++i) {std::shared_lock<std::shared_mutex> lock(rw_mutex); // 共享锁(读锁)std::cout << "Reader " << id << ": " << shared_data << std::endl;}
}// 写操作(独占)
void writer() {for (int i = 0; i < 10; ++i) {std::unique_lock<std::shared_mutex> lock(rw_mutex); // 独占锁(写锁)++shared_data;std::cout << "Writer updated: " << shared_data << std::endl;}
}int main() {std::vector<std::thread> readers;for (int i = 0; i < 5; ++i) {readers.emplace_back(reader, i);}std::thread w(writer);for (auto& t : readers) t.join();w.join();return 0;
}
std::shared_lock:用于读操作,允许多个线程同时持有。
std::unique_lock:用于写操作,独占资源,同一时间仅允许一个线程持有。
三、高级锁管理(std::unique_lock)
std::unique_lock 是一种比 std::lock_guard (自动加锁)更灵活的锁管理工具,主要用于需要手动控制锁的生命周期的场景。
比如说想在某一处执行的地方,某一个变量的赋值之前加锁,赋值完毕之后解锁,比较灵活,但是需要注意解锁。
#include <mutex>
#include <thread>std::mutex mtx;void worker() {std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 构造时不加锁// 执行无需锁的操作...lock.lock(); // 手动加锁// 临界区lock.unlock(); // 手动解锁// 执行其他无需锁的操作...
}
std::unique_lock 在析构时会自动解锁,因此忘记手动解锁不会导致死锁,但可能导致锁持有时间过长,降低并发性能。
四、死锁
https://www.51cto.com/article/623760.html
1、死锁条件
死锁的四个条件:
● 不可抢占(no preemption):系统资源不能被强制从一个进程(线程)中退出,已经获得的资源在未使用完之前不能被抢占。
● 占有并等待(hold and wait):一个进程(线程)因请求资源阻塞时,对已获得的资源保持不放。
● 互斥(mutual exclusion):资源只能同时分配给一个进程(线程),无法多个进程(线程)共享。
● 循环等待(circular waiting):一系列进程(线程)互相持有其他进程(线程)所需要的资源。
只有同时满足以上四个条件,才会产生死锁,想要消除死锁只需要破坏其中任意一个条件即可。
using std::cout; std::mutex mutex1;
std::mutex mutex2;
std::mutex mutex3; void FuncA() { std::lock_guard<std::mutex> guard1(mutex1); // 获取mutex1std::this_thread::sleep_for(std::chrono::seconds(1)); // 休眠1秒std::lock_guard<std::mutex> guard2(mutex2); // 获取mutex2(如果已被B持有,则阻塞)std::this_thread::sleep_for(std::chrono::seconds(1)); // 休眠1秒
} void FuncB() { std::lock_guard<std::mutex> guard2(mutex2); // 获取mutex2std::this_thread::sleep_for(std::chrono::seconds(1)); // 休眠1秒std::lock_guard<std::mutex> guard3(mutex3); // 获取mutex3(如果已被C持有,则阻塞)std::this_thread::sleep_for(std::chrono::seconds(1)); // 休眠1秒
} void FuncC() { std::lock_guard<std::mutex> guard3(mutex3); // 获取mutex3std::this_thread::sleep_for(std::chrono::seconds(1)); // 休眠1秒std::lock_guard<std::mutex> guard1(mutex1); // 获取mutex1(如果已被A持有,则阻塞)std::this_thread::sleep_for(std::chrono::seconds(1)); // 休眠1秒
} int main() { std::thread A(FuncA); std::thread B(FuncB); std::thread C(FuncC); std::this_thread::sleep_for(std::chrono::seconds(5)); // 尝试回收子线程if (A.joinable()) A.join(); if (B.joinable()) B.join(); if (C.joinable()) C.join(); cout << "hello\n"; // 永远不会执行 return 0;
}
2、gdb调试死锁
直接gdb+可执行程序,应该会什么都不打印,一直鼠标跳动。
线程 1 (LWP 3097)
- 角色:主线程(从
main
函数启动的线程)。 - 状态:阻塞在
futex
系统调用,等待某个条件(可能是等待子线程结束)。 - 关键点:
expected=3100
表明它在等待线程 ID 为 3100 的子线程(即 LWP 3100)。
线程 2 (LWP 3100)
- 角色:执行
FuncA()
的线程(根据锁地址匹配)。 - 状态:阻塞在
mutex2
(地址0x55555555a1a0
)上。 - 关键点:
expected=2
表示它期望锁的状态为 2(可能是 “已锁定”),但当前锁被其他线程持有。
线程 3 (LWP 3101)
- 角色:执行
FuncB()
的线程。 - 状态:阻塞在
mutex3
(地址0x55555555a1e0
)上。
线程 4 (LWP 3102)
-
角色:执行
FuncC()
的线程。 -
状态:阻塞在
mutex1
(地址0x55555555a160
)上。 -
等待的锁:线程 2 在等待
mutex2
,线程 3 在等待mutex3
,线程 4 在等待mutex1
。 -
已持有的锁:需结合源代码逻辑推断(例如线程 2 已持有
mutex1
,因为它在等待mutex2
之前获取了mutex1
)。
死锁原因分析:
结合你的原始代码:
- 线程 2(FuncA) 已持有
mutex1
,正在等待mutex2
。 - 线程 3(FuncB) 已持有
mutex2
,正在等待mutex3
。 - 线程 4(FuncC) 已持有
mutex3
,正在等待mutex1
。
形成循环依赖链:线程 2 → 线程 3 → 线程 4 → 线程 2,导致死锁。
死锁原因分析:
结合你的原始代码:
- 线程 2(FuncA) 已持有
mutex1
,正在等待mutex2
。 - 线程 3(FuncB) 已持有
mutex2
,正在等待mutex3
。 - 线程 4(FuncC) 已持有
mutex3
,正在等待mutex1
。
形成循环依赖链:线程 2 → 线程 3 → 线程 4 → 线程 2,导致死锁。
(gdb) thread apply all bt 也可以运行以下命令查看所有线程的堆栈