C++11鎖機(jī)制mutex、lock_guard與unique_lock詳細(xì)解析
大家好啊,我是小康。今天咱們聊點(diǎn)"家常"——那些讓C++程序員又愛(ài)又恨的多線程同步工具!
如果你曾經(jīng)被多線程搞得頭大,或者聽(tīng)到"死鎖"就心慌,那這篇文章就是為你準(zhǔn)備的。今天我要用最接地氣的方式,幫你徹底搞懂C++11中的三兄弟:mutex、lock_guard和unique_lock。
為啥要用這些同步工具?
先別急著學(xué)怎么用,咱們得先知道為啥要用??!
想象一下:你和室友共用一個(gè)衛(wèi)生間。如果你們同時(shí)沖進(jìn)去...嗯,畫(huà)面太美不敢想象。所以你們會(huì)怎么做?肯定是先看看有沒(méi)有人,沒(méi)人才進(jìn)去,然后反鎖門(mén),用完了再開(kāi)門(mén)。
多線程程序也一樣!不同的線程可能會(huì)同時(shí)訪問(wèn)同一塊"地盤(pán)"(共享資源),如果不加控制,就會(huì)出現(xiàn)數(shù)據(jù)錯(cuò)亂、程序崩潰等一系列災(zāi)難。
這時(shí)候,我們的三兄弟就閃亮登場(chǎng)了!
老大:mutex(互斥鎖)
mutex就像那個(gè)衛(wèi)生間的門(mén)鎖,它是最基礎(chǔ)的同步工具,核心功能就兩個(gè):鎖上(lock)和開(kāi)鎖(unlock)。
來(lái)看個(gè)最簡(jiǎn)單的例子:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 這就是我們的"門(mén)鎖"
int shared_value = 0; // 這是我們要保護(hù)的"衛(wèi)生間"
void increment_value() {
mtx.lock(); // 進(jìn)去之前先鎖門(mén)
std::cout << "線程 " << std::this_thread::get_id() << " 進(jìn)入臨界區(qū)" << std::endl;
// 想象這是個(gè)很復(fù)雜的操作,需要一些時(shí)間
shared_value++;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "線程 " << std::this_thread::get_id() << " 即將離開(kāi),共享值為: " << shared_value << std::endl;
mtx.unlock(); // 用完了記得開(kāi)鎖,讓別人能進(jìn)來(lái)
}
int main() {
std::thread t1(increment_value);
std::thread t2(increment_value);
t1.join();
t2.join();
return 0;
}看著挺簡(jiǎn)單對(duì)吧?但這有個(gè)大坑——如果在lock和unlock之間發(fā)生了異常,或者你單純忘記了unlock,那么鎖就永遠(yuǎn)不會(huì)被釋放,其他線程永遠(yuǎn)進(jìn)不了"衛(wèi)生間"!這就是傳說(shuō)中的"死鎖"。
正因如此,直接使用mutex很容易出錯(cuò),所以C++11給我們提供了更智能的解決方案。
老二:lock_guard(保安大哥)
lock_guard就像一個(gè)靠譜的保安大哥。當(dāng)你進(jìn)"衛(wèi)生間"時(shí),他會(huì)自動(dòng)鎖門(mén);當(dāng)你出來(lái)時(shí),無(wú)論是正常出來(lái)還是因?yàn)橥话l(fā)情況(異常)跑出來(lái),他都會(huì)負(fù)責(zé)解鎖。
看看用lock_guard如何改寫(xiě)上面的例子:
void safer_increment() {
std::lock_guard<std::mutex> guard(mtx); // 保安上崗,自動(dòng)鎖門(mén)
std::cout << "線程 " << std::this_thread::get_id() << " 進(jìn)入臨界區(qū)" << std::endl;
// 即使這里拋出異常,離開(kāi)函數(shù)作用域時(shí)lock_guard也會(huì)自動(dòng)解鎖
shared_value++;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "線程 " << std::this_thread::get_id() << " 即將離開(kāi),共享值為: " << shared_value << std::endl;
// 不需要手動(dòng)解鎖,guard離開(kāi)作用域時(shí)會(huì)自動(dòng)解鎖
}是不是簡(jiǎn)單多了?這就是RAII(資源獲取即初始化)的魅力——資源的管理跟對(duì)象的生命周期綁定在一起。lock_guard一旦創(chuàng)建就會(huì)鎖定互斥量,一旦銷毀(離開(kāi)作用域)就會(huì)解鎖互斥量。
不過(guò)lock_guard有個(gè)局限性:一旦上鎖,在其生命周期內(nèi)你就不能手動(dòng)解鎖了。就像你請(qǐng)了個(gè)特別死板的保安,他堅(jiān)持要等你徹底離開(kāi)才會(huì)開(kāi)門(mén),中途想出去透?jìng)€(gè)氣都不行。
老三:unique_lock(萬(wàn)能管家)
如果說(shuō)lock_guard是保安大哥,那unique_lock就是一個(gè)高級(jí)管家,不但能自動(dòng)鎖門(mén)解鎖,還能根據(jù)你的指令隨時(shí)鎖門(mén)或開(kāi)門(mén),甚至可以"借"鑰匙給別人。
來(lái)看個(gè)例子:
void flexible_operation() {
std::unique_lock<std::mutex> superlock(mtx); // 默認(rèn)情況下構(gòu)造時(shí)會(huì)鎖定mutex
std::cout << "線程 " << std::this_thread::get_id() << " 開(kāi)始工作" << std::endl;
shared_value++;
// 假設(shè)這里不需要鎖了,可以提前解鎖
superlock.unlock();
std::cout << "臨時(shí)解鎖,執(zhí)行一些不需要保護(hù)的操作" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(50));
// 需要再次訪問(wèn)共享資源時(shí),可以重新上鎖
superlock.lock();
shared_value++;
std::cout << "線程 " << std::this_thread::get_id() << " 完成工作,共享值為: " << shared_value << std::endl;
// 同樣,不需要手動(dòng)解鎖,離開(kāi)作用域時(shí)會(huì)自動(dòng)解鎖(如果當(dāng)時(shí)處于鎖定狀態(tài))
}除了手動(dòng)lock和unlock,unique_lock還有更多高級(jí)功能:
std::unique_lock<std::mutex> master_lock(mtx, std::defer_lock); // 創(chuàng)建時(shí)不鎖定
if (master_lock.try_lock()) { // 嘗試鎖定,如果失敗也不會(huì)阻塞
std::cout << "成功獲取鎖!" << std::endl;
} else {
std::cout << "獲取鎖失敗,但我可以去做別的事" << std::endl;
}
// 還可以配合條件變量使用
std::condition_variable cv;
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; }); // 這里會(huì)自動(dòng)解鎖并等待條件滿足unique_lock比lock_guard靈活,但也付出了一點(diǎn)性能代價(jià),它內(nèi)部需要維護(hù)更多狀態(tài)信息。
三兄弟大比拼
說(shuō)了這么多,來(lái)個(gè)簡(jiǎn)單對(duì)比:
| 特性 | mutex | lock_guard | unique_lock |
|---|---|---|---|
| 手動(dòng)鎖定/解鎖 | ? | ? | ? |
| 異常安全 | ?(需手動(dòng)保證) | ? | ? |
| 條件變量配合 | ? | ? | ? |
| 嘗試鎖定(try_lock) | ? | ? | ? |
| 性能開(kāi)銷 | 最小 | 很小 | 稍大 |
| 使用難度 | 容易出錯(cuò) | 簡(jiǎn)單安全 | 靈活但復(fù)雜 |
實(shí)戰(zhàn):模擬ATM取款與系統(tǒng)維護(hù)
最后用一個(gè)貼近生活的例子來(lái)鞏固一下。假設(shè)我們有個(gè)ATM系統(tǒng),既要處理用戶取款,又要處理銀行的系統(tǒng)維護(hù):
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
class ATMSystem {
private:
double cash_available; // ATM中可用現(xiàn)金
bool maintenance_mode; // 是否處于維護(hù)模式
std::mutex mtx;
std::condition_variable cv; // 條件變量,用于等待維護(hù)結(jié)束
public:
ATMSystem(double initial_cash) : cash_available(initial_cash), maintenance_mode(false) {}
// 用戶取款操作
bool withdraw(double amount) {
// 這里必須用unique_lock,因?yàn)闂l件變量wait需要它
std::unique_lock<std::mutex> lock(mtx);
// 如果ATM正在維護(hù)中,等待維護(hù)結(jié)束
cv.wait(lock, [this] { return !maintenance_mode; });
// 檢查余額并取款
if (cash_available >= amount) {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
cash_available -= amount;
std::cout << "取出: " << amount << ",ATM剩余現(xiàn)金: " << cash_available << std::endl;
return true;
}
std::cout << "ATM現(xiàn)金不足,取款失??!當(dāng)前剩余: " << cash_available << std::endl;
return false;
}
// 開(kāi)始系統(tǒng)維護(hù)
void start_maintenance() {
std::lock_guard<std::mutex> guard(mtx);
maintenance_mode = true;
std::cout << "ATM進(jìn)入維護(hù)模式,暫停服務(wù)" << std::endl;
}
// 結(jié)束系統(tǒng)維護(hù)
void end_maintenance() {
{
std::lock_guard<std::mutex> guard(mtx);
maintenance_mode = false;
std::cout << "ATM維護(hù)完成,恢復(fù)服務(wù)" << std::endl;
}
// 通知所有等待的取款線程
cv.notify_all();
}
// 補(bǔ)充現(xiàn)金
void refill_cash(double amount) {
std::lock_guard<std::mutex> guard(mtx);
cash_available += amount;
std::cout << "ATM補(bǔ)充現(xiàn)金: " << amount << ",當(dāng)前總現(xiàn)金: " << cash_available << std::endl;
}
};
// 模擬用戶線程
void user_thread(ATMSystem& atm, int user_id) {
std::cout << "用戶 " << user_id << " 嘗試取款..." << std::endl;
atm.withdraw(100);
}
// 模擬維護(hù)線程
void maintenance_thread(ATMSystem& atm) {
std::this_thread::sleep_for(std::chrono::milliseconds(20));
atm.start_maintenance();
// 執(zhí)行維護(hù)操作
std::this_thread::sleep_for(std::chrono::milliseconds(300));
atm.refill_cash(500);
// 維護(hù)結(jié)束
atm.end_maintenance();
}
int main() {
ATMSystem atm(300); // 初始現(xiàn)金300元
// 啟動(dòng)一個(gè)維護(hù)線程和多個(gè)用戶線程
std::thread maint(maintenance_thread, std::ref(atm));
std::vector<std::thread> users;
for (int i = 1; i <= 5; ++i) {
users.push_back(std::thread(user_thread, std::ref(atm), i));
}
// 等待所有線程結(jié)束
maint.join();
for (auto& t : users) {
t.join();
}
return 0;
}總結(jié)
- mutex:最基礎(chǔ)的鎖,需要手動(dòng)鎖定和解鎖,用不好容易出問(wèn)題,就像自己管理衛(wèi)生間門(mén)鎖。
- lock_guard:簡(jiǎn)單安全的自動(dòng)鎖,構(gòu)造時(shí)鎖定,析構(gòu)時(shí)解鎖,但不能中途操作鎖狀態(tài),就像請(qǐng)了個(gè)死板但可靠的保安。
- unique_lock:功能最全面的鎖包裝器,靈活性最高,但有輕微的性能開(kāi)銷,就像一個(gè)萬(wàn)能的管家。
最佳實(shí)踐:
- 簡(jiǎn)單場(chǎng)景,優(yōu)先使用
lock_guard - 需要條件變量或靈活鎖定/解鎖時(shí),使用
unique_lock - 對(duì)性能極度敏感的場(chǎng)景,考慮直接使用
mutex,但要非常小心
希望這篇文章能讓你對(duì)C++11的同步工具有個(gè)清晰的認(rèn)識(shí)。多線程不再可怕,熟練掌握這"三兄弟",你就能寫(xiě)出安全高效的并發(fā)程序啦!
到此這篇關(guān)于C++11鎖機(jī)制三兄弟大比拼:mutex、lock_guard與unique_lock的文章就介紹到這了,更多相關(guān)C++ mutex、lock_guard與unique_lock內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談C++的語(yǔ)句語(yǔ)法與強(qiáng)制數(shù)據(jù)類型轉(zhuǎn)換
這篇文章主要介紹了淺談C++的語(yǔ)句語(yǔ)法與強(qiáng)制數(shù)據(jù)類型轉(zhuǎn)換,是C++入門(mén)學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-09-09
C語(yǔ)言實(shí)現(xiàn)字符串相連的四種方法
本文介紹了C語(yǔ)言中四種常見(jiàn)的字符串連接方法:strcat函數(shù)直接連接,sprintf函數(shù)格式化輸出,先復(fù)制后連接(strcpy+strcat),以及使用指針操作,詳細(xì)展示了每個(gè)方法的代碼示例,需要的朋友可以參考下2025-07-07
C語(yǔ)言實(shí)現(xiàn)掃雷游戲詳細(xì)流程
windows自帶的游戲《掃雷》是陪伴了無(wú)數(shù)人的經(jīng)典游戲,本文將利用C語(yǔ)言實(shí)現(xiàn)這一經(jīng)典的游戲,文中的示例代碼講解詳細(xì),感興趣的可以學(xué)習(xí)一下2022-05-05
Qt GUI圖形圖像開(kāi)發(fā)之QT表格控件QTableView詳細(xì)使用方法與實(shí)例
這篇文章主要介紹了Qt GUI圖形圖像開(kāi)發(fā)之QT表格控件QTableView詳細(xì)使用方法與實(shí)例,需要的朋友可以參考下2020-03-03
C語(yǔ)言實(shí)現(xiàn)賓館管理系統(tǒng)課程設(shè)計(jì)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)賓館管理系統(tǒng)課程設(shè)計(jì),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
Qt中const?QString轉(zhuǎn)換?char?*可能的坑
本文主要介紹了Qt中const?QString轉(zhuǎn)換?char?*可能的坑,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07

