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

