【學習筆記】鎖+死鎖+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 也可以運行以下命令查看所有線程的堆棧