C++ 多線程同步之互斥鎖(mutex)實戰(zhàn)指南
?? 學習目標:掌握 C++ 標準庫中互斥鎖的基本用法,理解多線程同步的核心原理,能夠解決多線程環(huán)境下的資源競爭問題。
?? 學習重點:std::mutex 與 std::lock_guard 的使用、死鎖的產(chǎn)生原因及規(guī)避方法、實際場景中的同步案例實現(xiàn)。
48.1 多線程同步的必要性
在多線程編程中,當多個線程同時訪問共享資源時,會出現(xiàn)資源競爭問題。
例如兩個線程同時對同一個變量進行讀寫操作,會導致最終結果與預期不符。
這種問題被稱為線程安全問題,而解決該問題的核心就是線程同步。
?? 注意事項:線程不同步會引發(fā)數(shù)據(jù)競爭,造成程序運行結果不可預測,甚至導致程序崩潰。
舉個簡單的反例,兩個線程同時對全局變量 count 進行自增操作:
#include <iostream>
#include <thread>
using namespace std;
int count = 0;
void increment() {
for (int i = 0; i < 100000; ++i) {
count++; // 非原子操作,存在數(shù)據(jù)競爭
}
}
int main() {
thread t1(increment);
thread t2(increment);
t1.join();
t2.join();
cout << "最終 count 值:" << count << endl;
return 0;
}運行該程序會發(fā)現(xiàn),最終 count 的值大概率小于 200000。
這就是因為 count++ 不是原子操作,被兩個線程交替執(zhí)行打亂了執(zhí)行步驟。
48.2 C++ 標準庫中的互斥鎖
C++11 及以后的標準庫提供了 <mutex> 頭文件,封裝了多種互斥鎖相關的類。
最基礎且常用的就是 std::mutex。
48.2.1std::mutex的核心接口
lock():獲取互斥鎖。如果鎖已被其他線程占用,當前線程會阻塞等待。unlock():釋放互斥鎖。必須與lock()成對使用。try_lock():嘗試獲取互斥鎖。如果獲取失敗,不會阻塞,直接返回false。
48.2.2std::lock_guard:自動管理鎖的生命周期
直接使用 lock() 和 unlock() 容易出現(xiàn)遺漏解鎖的情況。
比如程序拋出異常時,unlock() 可能無法執(zhí)行,導致死鎖。std::lock_guard 基于RAII 機制實現(xiàn),可以自動在構造時加鎖,析構時解鎖。
? 核心結論:實際開發(fā)中優(yōu)先使用 std::lock_guard,而非手動調(diào)用 lock()/unlock()。
48.3 互斥鎖實戰(zhàn):解決數(shù)據(jù)競爭問題
我們使用 std::mutex 和 std::lock_guard 改造 48.1 節(jié)的反例:
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
int count = 0;
mutex mtx; // 定義全局互斥鎖
void increment() {
for (int i = 0; i < 100000; ++i) {
lock_guard<mutex> lock(mtx); // 自動加鎖
count++; // 臨界區(qū)代碼,此時只有一個線程能執(zhí)行
} // lock_guard 析構,自動解鎖
}
int main() {
thread t1(increment);
thread t2(increment);
t1.join();
t2.join();
cout << "最終 count 值:" << count << endl;
return 0;
}運行該程序,最終 count 的值穩(wěn)定等于 200000。
這說明互斥鎖成功保護了臨界區(qū)代碼,避免了數(shù)據(jù)競爭。
48.3.1 關鍵概念解釋
- 臨界區(qū):需要被保護的、不能被多個線程同時執(zhí)行的代碼段。
上例中count++就是臨界區(qū)。 - 互斥鎖的作用:保證同一時刻只有一個線程能進入臨界區(qū)。
48.4 死鎖的產(chǎn)生與規(guī)避
?? 死鎖:多個線程互相持有對方需要的鎖,導致所有線程都無法繼續(xù)執(zhí)行的狀態(tài)。
48.4.1 死鎖的四個必要條件
- 互斥條件:資源只能被一個線程占用。
- 請求與保持條件:線程持有一個資源的同時,請求其他線程持有的資源。
- 不可剝奪條件:線程已持有的資源不能被其他線程強制奪走。
- 循環(huán)等待條件:多個線程形成首尾相接的循環(huán)等待資源關系。
48.4.2 死鎖的示例
兩個線程分別持有一個鎖,同時請求對方的鎖:
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex mtx1, mtx2;
void thread1() {
mtx1.lock();
this_thread::sleep_for(chrono::milliseconds(100)); // 確保 thread2 先拿到 mtx2
mtx2.lock(); // 等待 mtx2,此時 thread2 持有 mtx2 并等待 mtx1
cout << "thread1 執(zhí)行完畢" << endl;
mtx2.unlock();
mtx1.unlock();
}
void thread2() {
mtx2.lock();
this_thread::sleep_for(chrono::milliseconds(100)); // 確保 thread1 先拿到 mtx1
mtx1.lock(); // 等待 mtx1,此時 thread1 持有 mtx1 并等待 mtx2
cout << "thread2 執(zhí)行完畢" << endl;
mtx1.unlock();
mtx2.unlock();
}
int main() {
thread t1(thread1);
thread t2(thread2);
t1.join();
t2.join();
return 0;
}運行該程序,兩個線程會互相等待,陷入死鎖狀態(tài),無法輸出任何內(nèi)容。
48.4.3 規(guī)避死鎖的常用方法
- 固定鎖的獲取順序:所有線程按照相同的順序獲取鎖。
比如上例中,讓兩個線程都先獲取mtx1,再獲取mtx2。 - 使用
std::lock同時獲取多個鎖:std::lock可以一次性獲取多個互斥鎖,避免循環(huán)等待。 - 使用帶超時的鎖嘗試:通過
try_lock()或std::timed_mutex,在超時后放棄獲取鎖,避免永久阻塞。
48.5 實戰(zhàn)案例:多線程售票系統(tǒng)
模擬一個售票系統(tǒng),多個窗口同時售票,使用互斥鎖保證票數(shù)不會出現(xiàn)負數(shù)或重復售票的情況。
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
using namespace std;
int tickets = 100; // 總票數(shù)
mutex mtx;
// 售票函數(shù)
void sell_tickets(int window_id) {
while (true) {
lock_guard<mutex> lock(mtx);
if (tickets > 0) {
cout << "窗口" << window_id << "售出第" << tickets << "張票" << endl;
tickets--;
this_thread::sleep_for(chrono::milliseconds(50)); // 模擬售票耗時
} else {
break;
}
}
cout << "窗口" << window_id << "售票結束" << endl;
}
int main() {
vector<thread> windows;
// 創(chuàng)建 5 個售票窗口
for (int i = 1; i <= 5; ++i) {
windows.emplace_back(sell_tickets, i);
}
// 等待所有窗口售票結束
for (auto& t : windows) {
t.join();
}
cout << "所有票已售罄" << endl;
return 0;
}? 運行效果:5 個窗口有序售票,最終票數(shù)從 100 遞減到 0,不會出現(xiàn)重復售票或票數(shù)為負的情況。
48.6 本章小結
- 多線程訪問共享資源時必須進行同步,否則會出現(xiàn)數(shù)據(jù)競爭問題。
std::mutex是 C++ 最基礎的互斥鎖,搭配std::lock_guard可以安全地管理鎖的生命周期。- 死鎖由四個必要條件引發(fā),通過固定鎖順序、使用
std::lock等方法可以有效規(guī)避。 - 互斥鎖的核心是保護臨界區(qū),確保同一時刻只有一個線程能執(zhí)行臨界區(qū)代碼。
到此這篇關于C++ 多線程同步之互斥鎖(mutex)實戰(zhàn)的文章就介紹到這了,更多相關C++ 互斥鎖mutex內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C++實現(xiàn)LeetCode(160.求兩個鏈表的交點)
這篇文章主要介紹了C++實現(xiàn)LeetCode(160.求兩個鏈表的交點),本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-07-07
解決在Mac下直接解壓C++靜態(tài)庫出現(xiàn)的問題
最近在研究C++的各種編譯構建過程,學習了一下cmake,gyp/ninja這些自動化構建工具后,想著自己試下用純命令行跑一遍編譯流程。在試圖把C++靜態(tài)庫編譯為動態(tài)庫的過程中遇到了棘手的問題,找了好久后發(fā)現(xiàn)是跟Mac平臺相關的,這里記錄一下,望對遇到類似問題的童鞋有幫助。2016-12-12
Qt使用SQLite數(shù)據(jù)庫實現(xiàn)數(shù)據(jù)增刪改查
這篇文章主要為大家詳細介紹了Qt如何使用SQLite數(shù)據(jù)庫實現(xiàn)數(shù)據(jù)增刪改查功能,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起了解一下2023-06-06

