C++自旋鎖的實(shí)現(xiàn)示例
一、 什么是自旋鎖?
核心定義:
自旋鎖是一種非阻塞鎖。當(dāng)線(xiàn)程嘗試獲取鎖失敗時(shí),它不會(huì)掛起(阻塞/讓出 CPU),而是會(huì)在一個(gè)死循環(huán)中持續(xù)檢查(忙等待 / Busy-Waiting)鎖是否被釋放。
直觀隱喻:
互斥鎖 (Mutex):你去洗手間,發(fā)現(xiàn)門(mén)鎖了。你回到座位上睡覺(jué)。等里面的人出來(lái)了,管理員把你叫醒,你再去上。
- 開(kāi)銷(xiāo):睡覺(jué)(切換上下文)和被叫醒(調(diào)度)很累。
自旋鎖 (Spinlock):你去洗手間,發(fā)現(xiàn)門(mén)鎖了。你站在門(mén)口,每隔 0.1 秒就敲門(mén)問(wèn):“好了沒(méi)?好了沒(méi)?”,直到里面的人出來(lái)。
- 開(kāi)銷(xiāo):你一直站著(占用 CPU),哪里也去不了。但是一旦門(mén)開(kāi)了,你零延遲沖進(jìn)去。
二、 為什么需要自旋鎖?(底層視角)
您可能會(huì)問(wèn):“讓線(xiàn)程空轉(zhuǎn)浪費(fèi) CPU,這不是很傻嗎?”
要理解它的價(jià)值,必須看**上下文切換(Context Switch)**的成本。
- Mutex 的成本:
- 當(dāng)
std::mutex拿不到鎖時(shí),線(xiàn)程會(huì)陷入內(nèi)核態(tài)(Kernel Mode)。 - OS 需要保存當(dāng)前線(xiàn)程的寄存器、棧指針,刷新 TLB(頁(yè)表緩存),然后調(diào)度另一個(gè)線(xiàn)程。
- 這個(gè)過(guò)程大約需要 3 ~ 10 微秒(在現(xiàn)代 CPU 上)。
- Spinlock 的優(yōu)勢(shì):
- 如果您的臨界區(qū)代碼執(zhí)行時(shí)間極短(比如只是做一個(gè)
pNext = node;的鏈表操作),耗時(shí)可能只有 0.01 微秒。 - 為了等待 0.01 微秒的任務(wù),去花費(fèi) 5 微秒切換線(xiàn)程,是虧本生意。
- 自旋鎖全程在**用戶(hù)態(tài)(User Mode)**運(yùn)行,完全沒(méi)有系統(tǒng)調(diào)用開(kāi)銷(xiāo)。
結(jié)論:自旋鎖適用于**“鎖持有時(shí)間極短”**的場(chǎng)景。
三、 C++ 中的自旋鎖實(shí)現(xiàn)
C++ 標(biāo)準(zhǔn)庫(kù)并沒(méi)有直接提供 std::spinlock(C++20 只有 std::atomic_flag),我們需要利用原子操作自己實(shí)現(xiàn)。
1. 最基礎(chǔ)的實(shí)現(xiàn):std::atomic_flag
這是 C++ 中唯一保證**無(wú)鎖(Lock-Free)**的數(shù)據(jù)類(lèi)型。
#include <atomic>
#include <thread>
#include <vector>
#include <iostream>
class SpinLock {
private:
// atomic_flag 只有兩個(gè)狀態(tài):set (true) 和 clear (false)
// ATOMIC_FLAG_INIT 初始化為 false
std::atomic_flag flag = ATOMIC_FLAG_INIT;
public:
void lock() {
// test_and_set():
// 1. 讀取當(dāng)前值
// 2. 將值設(shè)為 true
// 3. 返回舊值
// 這是一個(gè)原子操作 (RMW: Read-Modify-Write)
// 如果返回 true,說(shuō)明之前已經(jīng)是 true (被別人鎖了),則一直循環(huán) (自旋)
// memory_order_acquire: 保證獲得鎖之后的讀寫(xiě)操作不會(huì)重排到加鎖之前
while (flag.test_and_set(std::memory_order_acquire)) {
// 這里是自旋區(qū) (Spinning)
// 可以在這里加 "CPU pause" 指令優(yōu)化(后面會(huì)講)
}
}
void unlock() {
// 清除標(biāo)志,設(shè)為 false
// memory_order_release: 保證解鎖之前的讀寫(xiě)操作全部完成
flag.clear(std::memory_order_release);
}
};
// 使用示例(配合 lock_guard 滿(mǎn)足 RAII)
SpinLock sl;
void worker() {
// std::lock_guard 需要類(lèi)滿(mǎn)足 BasicLockable (有 lock/unlock 方法)
std::lock_guard<SpinLock> guard(sl);
// 臨界區(qū)...
}2. 通用實(shí)現(xiàn):std::atomic<bool>
功能類(lèi)似,但 atomic<bool> 可以提供更多 API(比如 load 查看狀態(tài)),只是在極老的硬件上可能不是 Lock-Free 的(雖然現(xiàn)在幾乎都是)。
class SpinLockBool {
std::atomic<bool> locked{false};
public:
void lock() {
bool expected = false;
// CAS (Compare And Swap)
// 嘗試把 locked 從 false 改成 true
// 如果 locked 是 true (被鎖),compare_exchange_weak 返回 false,繼續(xù)循環(huán)
while (!locked.compare_exchange_weak(expected, true, std::memory_order_acquire)) {
expected = false; // CAS 失敗后 expected 會(huì)被改成當(dāng)前值(true),重置為 false 再次嘗試
}
}
void unlock() {
locked.store(false, std::memory_order_release);
}
};四、 致命陷阱與性能優(yōu)化(C++ 高階)
在實(shí)現(xiàn)高性能組件(如內(nèi)存池)時(shí),直接用 while(flag.test_and_set()) 會(huì)帶來(lái)嚴(yán)重的性能問(wèn)題。
1. 緩存一致性風(fēng)暴 (Cache Coherence Storm / Bus Contention)
現(xiàn)象:多個(gè)線(xiàn)程在一個(gè)原子變量上瘋狂 CAS(寫(xiě)操作)。
原理:根據(jù) CPU 的 MESI 協(xié)議,當(dāng)一個(gè)核修改原子變量時(shí),必須讓其他核的 Cache Line 失效。如果 10 個(gè)線(xiàn)程同時(shí)自旋,鎖變量所在的 Cache Line 會(huì)在 CPU 核心之間瘋狂“跳來(lái)跳去”,導(dǎo)致總線(xiàn)流量爆炸,甚至拖慢其他不相關(guān)線(xiàn)程的速度。
解決:Test-Test-and-Set (TTAS) 模式。
- 先用
load(讀) 檢查是否被釋放(讀操作不獨(dú)占 Cache Line)。 - 只有讀到
false時(shí),才嘗試CAS(寫(xiě))。
2. CPU 流水線(xiàn)空轉(zhuǎn)
現(xiàn)象:while 循環(huán)是一個(gè)極緊密的指令序列,CPU 流水線(xiàn)會(huì)全速運(yùn)行,產(chǎn)生大量熱量并消耗電力。
解決:CPU Pause 指令。
在 x86 架構(gòu)下,使用 _mm_pause() 指令(SSE2 擴(kuò)展)。
- 它告訴 CPU “我在自旋”,讓 CPU 稍微降低流水線(xiàn)派發(fā)速度,節(jié)能降溫。
- 它可以避免退出循環(huán)時(shí)的內(nèi)存順序沖突懲罰。
優(yōu)化后的 C++ 代碼:
#include <atomic>
#include <immintrin.h> // for _mm_pause
class OptimizedSpinLock {
std::atomic_flag flag = ATOMIC_FLAG_INIT;
public:
void lock() {
while (flag.test_and_set(std::memory_order_acquire)) {
// 在自旋期間...
while (flag.test(std::memory_order_relaxed)) { // 先只讀 (Test)
// 告訴 CPU 稍微休息一下,不要全速空轉(zhuǎn)
#if defined(__x86_64__) || defined(_M_X64)
_mm_pause();
#endif
// 如果是 ARM 架構(gòu),可以用 __yield() 或 asm("yield")
}
}
}
void unlock() {
flag.clear(std::memory_order_release);
}
};3. 優(yōu)先級(jí)反轉(zhuǎn)與死鎖
- 場(chǎng)景:如果一個(gè)低優(yōu)先級(jí)線(xiàn)程拿到了自旋鎖,但因?yàn)楸?OS 調(diào)度走了(時(shí)間片到了),一個(gè)高優(yōu)先級(jí)線(xiàn)程被調(diào)度進(jìn)來(lái),嘗試獲取同一個(gè)自旋鎖。
- 結(jié)果:高優(yōu)先級(jí)線(xiàn)程因?yàn)槭亲孕Φ龋?,它不讓?CPU,導(dǎo)致低優(yōu)先級(jí)線(xiàn)程永遠(yuǎn)得不到 CPU 來(lái)執(zhí)行解鎖操作。于是死鎖。
- 對(duì)策:在自旋鎖中,如果自旋超過(guò)一定次數(shù),必須使用
std::this_thread::yield()主動(dòng)讓出時(shí)間片。
到此這篇關(guān)于C++自旋鎖的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)C++自旋鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言函數(shù)棧幀的創(chuàng)建和銷(xiāo)毀詳解
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言函數(shù)棧幀的創(chuàng)建和銷(xiāo)毀,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-02-02
MATLAB實(shí)現(xiàn)五子棋游戲(雙人對(duì)戰(zhàn)、可悔棋)
這篇文章主要為大家詳細(xì)介紹了MATLAB實(shí)現(xiàn)五子棋游戲,可以進(jìn)行雙人對(duì)戰(zhàn)、也可悔棋,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-06-06
C++多重繼承引發(fā)的重復(fù)調(diào)用問(wèn)題與解決方法
這篇文章主要介紹了C++多重繼承引發(fā)的重復(fù)調(diào)用問(wèn)題與解決方法,結(jié)合具體實(shí)例形式分析了C++多重調(diào)用中的重復(fù)調(diào)用問(wèn)題及相應(yīng)的解決方法,需要的朋友可以參考下2018-05-05
C++實(shí)現(xiàn)LeetCode(151.翻轉(zhuǎn)字符串中的單詞)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(151.翻轉(zhuǎn)字符串中的單詞),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07
深入剖析Android中init進(jìn)程實(shí)現(xiàn)的C語(yǔ)言源碼
這篇文章主要介紹了Android中init進(jìn)程實(shí)現(xiàn)的C語(yǔ)言源碼,init屬性服務(wù)在安卓中屬于系統(tǒng)的底層Linux服務(wù),需要的朋友可以參考下2015-07-07
Qt采用線(xiàn)程以隊(duì)列方式實(shí)現(xiàn)下發(fā)數(shù)據(jù)
在C++中隊(duì)列是一種常用的數(shù)據(jù)結(jié)構(gòu)之一,一種特殊的線(xiàn)性表,一般采用先進(jìn)先出的方式。本文主要為大家介紹了Qt如何以隊(duì)列方式實(shí)現(xiàn)下發(fā)數(shù)據(jù),感興趣的可以了解一下2022-10-10
一篇文章帶你了解C語(yǔ)言?xún)?nèi)存對(duì)齊公式
這篇文章主要介紹了C語(yǔ)言?xún)?nèi)存對(duì)齊,包括內(nèi)存對(duì)其的基本概念及用法,以及注意事項(xiàng),并以實(shí)例形式加以說(shuō)明,需要的朋友可以參考下,希望能給你帶來(lái)幫助2021-08-08
vscode工程中c_cpp_properties.json文件作用詳細(xì)說(shuō)明
c_cpp_properties.json是Visual Studio Code的一個(gè)配置文件,用于定義C/C++編譯器的路徑、默認(rèn)包含路徑和預(yù)處理器定義,這篇文章主要給大家介紹了關(guān)于vscode工程中c_cpp_properties.json文件作用詳細(xì)說(shuō)明的相關(guān)資料,需要的朋友可以參考下2024-08-08

