C++線程鎖的使用
一、基礎(chǔ)概念:鎖的核心分類
在講解具體工具前,先明確C++鎖的兩個(gè)核心維度:
- 基礎(chǔ)鎖類型(提供原始的加鎖/解鎖能力):std::mutex、std::recursive_mutex、std::timed_mutex、std::recursive_timed_mutex;
- RAII鎖封裝(管理基礎(chǔ)鎖的生命周期,避免手動(dòng)解鎖漏解/死鎖):std::lock_guard、std::unique_lock、std::scoped_lock(C++17)。
二、基礎(chǔ)鎖類型(原始鎖)
這類鎖是“底層同步工具”,提供lock()、unlock()、try_lock()等核心方法,但不建議直接使用(手動(dòng)解鎖易出錯(cuò)),需配合RAII封裝使用。
| 鎖類型 | 核心特性 | 適用場景 | 注意事項(xiàng) |
|---|---|---|---|
| std::mutex | 最基礎(chǔ)的互斥鎖,非遞歸、非超時(shí) | 絕大多數(shù)單資源互斥場景 | 同一線程重復(fù)lock()會(huì)觸發(fā)未定義行為(崩潰) |
| std::recursive_mutex | 遞歸互斥鎖,同一線程可多次lock()(需對應(yīng)次數(shù)unlock()) | 函數(shù)嵌套調(diào)用需要加鎖的場景 | 性能略低于std::mutex,避免濫用 |
| std::timed_mutex | 帶超時(shí)的互斥鎖,支持try_lock_for()(超時(shí)時(shí)間)、try_lock_until()(截止時(shí)間) | 需要“非阻塞等待鎖”的場景 | 超時(shí)返回false,避免永久阻塞 |
| std::recursive_timed_mutex | 遞歸+超時(shí)的互斥鎖,結(jié)合前兩者特性 | 遞歸調(diào)用+超時(shí)等待的場景 | 盡量少用,遞歸鎖易隱藏邏輯問題 |
基礎(chǔ)鎖使用示例(不推薦直接用,僅演示)
#include <mutex>
#include <iostream>
std::mutex m;
void bad_use() {
m.lock(); // 手動(dòng)加鎖
std::cout << "臨界區(qū)操作" << std::endl;
// 若此處拋異常,unlock()不會(huì)執(zhí)行 → 死鎖!
m.unlock(); // 手動(dòng)解鎖
}
核心問題:手動(dòng)管理
lock()/unlock()易因異常、邏輯分支遺漏解鎖,導(dǎo)致死鎖——這也是RAII封裝的核心價(jià)值。
三、RAII鎖封裝(推薦使用)
RAII(資源獲取即初始化)的核心思想:構(gòu)造時(shí)獲取資源,析構(gòu)時(shí)釋放資源。鎖封裝會(huì)在構(gòu)造函數(shù)中調(diào)用lock()(或接管已鎖定的鎖),析構(gòu)函數(shù)中調(diào)用unlock(),無論函數(shù)正常退出還是異常退出,鎖都會(huì)被釋放。
1. std::lock_guard(極簡RAII鎖,C++11)
核心特性
- 不可移動(dòng)、不可復(fù)制,生命周期與作用域綁定;
- 構(gòu)造時(shí)必須指定鎖,且默認(rèn)立即加鎖;
- 析構(gòu)時(shí)自動(dòng)解鎖,無其他額外功能(極簡、高效);
- 不支持手動(dòng)解鎖、不支持超時(shí)、不支持延遲加鎖。
使用示例(最常用場景)
#include <mutex>
#include <list>
std::list<int> data;
std::mutex m;
void safe_add(int val) {
// 構(gòu)造時(shí)加鎖,函數(shù)結(jié)束析構(gòu)時(shí)解鎖
std::lock_guard<std::mutex> guard(m);
data.push_back(val); // 臨界區(qū)操作,線程安全
}
進(jìn)階用法:接管已鎖定的鎖(std::adopt_lock)
當(dāng)鎖已被std::lock()手動(dòng)鎖定時(shí),用std::adopt_lock標(biāo)記告訴lock_guard“鎖已鎖定,只需接管解鎖責(zé)任”:
void swap_data(std::list<int>& a, std::list<int>& b, std::mutex& m1, std::mutex& m2) {
if (&a == &b) return;
std::lock(m1, m2); // 同時(shí)鎖定兩個(gè)鎖,避免死鎖
// adopt_lock:不重復(fù)加鎖,僅接管解鎖
std::lock_guard<std::mutex> g1(m1, std::adopt_lock);
std::lock_guard<std::mutex> g2(m2, std::adopt_lock);
a.swap(b); // 臨界區(qū)操作
}
適用場景
- 簡單的“作用域內(nèi)加鎖”場景,無需手動(dòng)控制鎖的生命周期;
- 追求極致性能(無額外開銷),只需“加鎖→操作→解鎖”的固定流程。
2. std::unique_lock(靈活RAII鎖,C++11)
核心特性
- 不可復(fù)制、可移動(dòng),生命周期可手動(dòng)控制;
- 支持延遲加鎖(構(gòu)造時(shí)不加鎖,后續(xù)手動(dòng)
lock()); - 支持手動(dòng)解鎖(
unlock())、重新加鎖(lock()); - 支持超時(shí)加鎖(
try_lock_for()、try_lock_until(),需配合timed_mutex); - 支持
std::adopt_lock接管已鎖定的鎖; - 性能略高于
lock_guard(但可忽略),功能遠(yuǎn)更靈活。
核心用法示例
(1)延遲加鎖(std::defer_lock)
#include <mutex>
std::timed_mutex tm;
void flexible_lock() {
// defer_lock:構(gòu)造時(shí)不加鎖,僅關(guān)聯(lián)鎖對象
std::unique_lock<std::timed_mutex> ul(tm, std::defer_lock);
// 手動(dòng)加鎖(也可嘗試超時(shí)加鎖)
if (ul.try_lock_for(std::chrono::seconds(1))) {
std::cout << "獲取鎖成功,執(zhí)行臨界區(qū)操作" << std::endl;
ul.unlock(); // 手動(dòng)解鎖(可提前釋放鎖,提高并發(fā))
// ... 其他非臨界區(qū)操作
ul.lock(); // 重新加鎖
} else {
std::cout << "1秒內(nèi)未獲取鎖,執(zhí)行降級(jí)邏輯" << std::endl;
}
// 析構(gòu)時(shí):如果鎖仍被持有,自動(dòng)解鎖
}
(2)配合條件變量(唯一適用場景)
std::condition_variable的wait()方法必須接收std::unique_lock(因?yàn)?code>wait()會(huì)臨時(shí)解鎖,被喚醒后重新加鎖,lock_guard無此能力):
#include <mutex>
#include <condition_variable>
#include <queue>
std::queue<int> q;
std::mutex m;
std::condition_variable cv;
void producer(int val) {
std::lock_guard<std::mutex> lg(m);
q.push(val);
cv.notify_one(); // 通知消費(fèi)者
}
void consumer() {
std::unique_lock<std::mutex> ul(m);
// wait()會(huì)解鎖,等待通知;被喚醒后重新加鎖,再判斷條件
cv.wait(ul, [](){ return !q.empty(); });
int val = q.front();
q.pop();
ul.unlock(); // 提前解鎖,提高并發(fā)
std::cout << "消費(fèi):" << val << std::endl;
}
適用場景
- 需要手動(dòng)控制鎖的生命周期(提前解鎖、重新加鎖);
- 需要超時(shí)等待鎖的場景;
- 配合
std::condition_variable使用(唯一選擇); - 復(fù)雜的加鎖邏輯(如條件加鎖、動(dòng)態(tài)解鎖)。
3. std::scoped_lock(C++17,lock_guard的升級(jí)版)
核心特性
- 不可移動(dòng)、不可復(fù)制,作用域綁定;
- 支持同時(shí)鎖定多個(gè)鎖(內(nèi)置
std::lock()的死鎖避免邏輯); - 性能與
lock_guard一致,語法更簡潔; - 可視為“多鎖版本的lock_guard”。
使用示例(替代lock_guard+std::lock)
#include <mutex>
#include <list>
std::list<int> a, b;
std::mutex m1, m2;
void safe_swap() {
// 同時(shí)鎖定m1和m2,自動(dòng)避免死鎖,析構(gòu)時(shí)同時(shí)解鎖
std::scoped_lock guard(m1, m2);
a.swap(b); // 臨界區(qū)操作
}
對比舊寫法(lock_guard+std::lock):
std::lock(m1, m2); std::lock_guard<std::mutex> g1(m1, std::adopt_lock); std::lock_guard<std::mutex> g2(m2, std::adopt_lock);
scoped_lock一行搞定,更簡潔、不易出錯(cuò)。
適用場景
- 需要同時(shí)鎖定多個(gè)鎖的場景(替代
std::lock + lock_guard); - C++17及以上環(huán)境,優(yōu)先選擇
scoped_lock而非lock_guard(功能更強(qiáng),無性能損失)。
四、其他鎖相關(guān)工具
1. std::call_once(單次調(diào)用鎖)
- 核心作用:保證某個(gè)函數(shù)在多線程環(huán)境下僅執(zhí)行一次(比如單例的初始化);
- 底層依賴
std::once_flag,比手動(dòng)加鎖判斷更高效、更安全。
使用示例
#include <mutex>
#include <iostream>
std::once_flag flag;
void init() {
std::cout << "僅執(zhí)行一次的初始化邏輯" << std::endl;
}
void thread_func() {
std::call_once(flag, init); // 多線程調(diào)用,init僅執(zhí)行一次
}
2. std::shared_mutex / std::shared_lock(C++17,讀寫鎖)
- 核心思想:區(qū)分“讀操作”和“寫操作”,允許多個(gè)讀線程同時(shí)持有鎖,寫線程獨(dú)占鎖(提高讀多寫少場景的并發(fā));
std::shared_lock:共享鎖(讀鎖),多個(gè)線程可同時(shí)持有;std::unique_lock:獨(dú)占鎖(寫鎖),僅一個(gè)線程持有。
使用示例
#include <shared_mutex>
#include <string>
#include <iostream>
std::string data = "初始數(shù)據(jù)";
std::shared_mutex sm;
// 讀線程:共享鎖
void read_data(int id) {
std::shared_lock<std::shared_mutex> sl(sm);
std::cout << "讀線程" << id << ":" << data << std::endl;
}
// 寫線程:獨(dú)占鎖
void write_data(const std::string& new_data) {
std::unique_lock<std::shared_mutex> ul(sm);
data = new_data;
std::cout << "寫線程:更新數(shù)據(jù)為" << new_data << std::endl;
}
優(yōu)勢:讀多寫少場景下,并發(fā)效率遠(yuǎn)高于普通互斥鎖(普通鎖無論讀寫都獨(dú)占)。
3. std::lock(通用鎖函數(shù))
- 核心作用:原子地鎖定多個(gè)鎖,避免死鎖(內(nèi)部實(shí)現(xiàn)按地址排序鎖定);
- 配合lock_guard/unique_lock的adopt_lock使用(C++17前);
- C++17后優(yōu)先用scoped_lock,無需手動(dòng)調(diào)用std::lock。
五、核心對比與選型建議
| 工具 | 核心優(yōu)勢 | 核心限制 | 優(yōu)先選型場景 |
|---|---|---|---|
| std::lock_guard | 極簡、高效 | 不可手動(dòng)解鎖、不支持多鎖 | C++11/14,單鎖、簡單作用域加鎖 |
| std::unique_lock | 靈活(延遲/超時(shí)/手動(dòng)解鎖) | 略高開銷(可忽略) | 條件變量、超時(shí)等待、動(dòng)態(tài)解鎖 |
| std::scoped_lock | 多鎖、簡潔、高效 | C++17+ | C++17+,單鎖/多鎖、所有簡單場景 |
| std::shared_mutex | 讀寫分離,讀多寫少并發(fā)高 | C++17+ | 讀多寫少的共享資源訪問 |
| std::call_once | 單次調(diào)用,無需手動(dòng)加鎖判斷 | 僅單次執(zhí)行 | 初始化、單例創(chuàng)建 |
選型口訣
- C++17及以上:優(yōu)先用scoped_lock(單鎖/多鎖),復(fù)雜邏輯用unique_lock;
- C++11/14:單鎖用lock_guard,多鎖用std::lock + lock_guard,復(fù)雜邏輯用unique_lock;
- 讀多寫少:用std::shared_mutex + shared_lock/unique_lock;
- 單次初始化:用std::call_once;
- 條件變量:必須用unique_lock。
六、總結(jié)
- 基礎(chǔ)鎖:std::mutex是核心,遞歸場景用recursive_mutex,超時(shí)場景用timed_mutex;
- RAII封裝:
- 簡單場景:lock_guard(C++11/14)/scoped_lock(C++17+);
- 復(fù)雜場景:unique_lock(靈活、支持條件變量/超時(shí));
- 特殊場景:
- 讀多寫少:shared_mutex + shared_lock;
- 單次執(zhí)行:call_once;
- 核心原則:永遠(yuǎn)用RAII封裝管理鎖,避免手動(dòng)lock()/unlock(),防止死鎖。
到此這篇關(guān)于C++線程鎖的使用的文章就介紹到這了,更多相關(guān)C++線程鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言輸出旋轉(zhuǎn)后數(shù)組中的最小數(shù)元素的算法原理與實(shí)例
這篇文章主要介紹了C語言輸出旋轉(zhuǎn)后數(shù)組中的最小數(shù)元素的算法原理與實(shí)例,數(shù)組旋轉(zhuǎn)就是把開頭的幾個(gè)指定的元素放到數(shù)組的末尾,需要的朋友可以參考下2016-03-03
C++中的運(yùn)算符和運(yùn)算符優(yōu)先級(jí)總結(jié)
這篇文章主要介紹了C++中的運(yùn)算符和運(yùn)算符優(yōu)先級(jí)總結(jié),主要整理了算術(shù)、關(guān)系、邏輯、位和賦值運(yùn)算符的用法,需要的朋友可以參考下2016-05-05
C語言編程中的聯(lián)合體union入門學(xué)習(xí)教程
這篇文章主要介紹了C語言編程中的聯(lián)合體union入門學(xué)習(xí)教程,也是C語言入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-12-12
C++實(shí)現(xiàn)有向圖鄰接表的構(gòu)建
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)有向圖鄰接表的構(gòu)建,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04
嵌入式C程序優(yōu)質(zhì)編寫全面教程規(guī)范
這是一年前我為公司內(nèi)部寫的一個(gè)文檔,旨在向年輕的嵌入式軟件工程師們介紹如何在裸機(jī)環(huán)境下編寫優(yōu)質(zhì)嵌入式C程序。感覺是有一定的參考價(jià)值,所以拿出來分享,拋磚引玉2022-04-04
C語言運(yùn)算符深入探究優(yōu)先級(jí)與結(jié)合性及種類
C語言運(yùn)算符號(hào)指的是運(yùn)算符號(hào)。C語言中的符號(hào)分為10類:算術(shù)運(yùn)算符、關(guān)系運(yùn)算符、邏輯運(yùn)算符、位操作運(yùn)算符、賦值運(yùn)算符、條件運(yùn)算符、逗號(hào)運(yùn)算符、指針運(yùn)算符、求字節(jié)數(shù)運(yùn)算符和特殊運(yùn)算符2022-05-05

