線程
常見問題
同步權限
在多線程 / 多進程并發時,為避免共享資源(如內存變量、硬件設備、文件)被同時修改導致的數據不一致,需要通過 “同步機制” 控制誰能訪問資源 ——“獲取同步權限” 就是線程 / 進程申請這種訪問資格的過程。
v4 = _InterlockedCompareExchange(a1, 1, 0);
- 第一個參數
a1
:指向目標內存地址的指針(通常是一個共享變量,如LONG*
類型),即要操作的 “共享資源標記”。 - 第二個參數
1
:當比較成功時,要寫入目標內存地址的 “新值”。 - 第三個參數
0
:“預期值”,即我們認為目標內存當前應該有的值。 - 返回值是操作前目標內存地址(
a1
指向的地址)中的原始值,
// 定義一個共享的“鎖標記”,0表示未占用,1表示已占用
LONG lock_flag = 0;// 線程A嘗試獲取鎖
LONG original = _InterlockedCompareExchange(&lock_flag, 1, 0);
if (original == 0) {//獲取到權限// 執行臨界區操作...// 操作完成后釋放鎖(如將lock_flag設回0)
} else {// 獲取鎖失敗(鎖已被其他線程占用)// 可選擇等待、重試或放棄
}
臨界區
臨界區(Critical Section) 指的是一段 “不能被多個線程同時執行” 的代碼片段,當一個線程正在執行臨界區代碼時,其他線程必須等待該線程執行完畢,才能進入同一臨界區。
原子性
“原子”(Atomic)描述的是一個不可分割、不可中斷的操作單元。一個 “原子操作” 要么完整地執行完畢,要么完全不執行,中間不會被任何其他線程、進程或中斷打斷,不存在 “執行到一半” 的中間狀態。
假設兩個線程(Thread A、Thread B)同時對共享變量 count
(初始值為 0)執行 count += 1
操作。
在底層會拆分為 3 個 CPU 指令:
- 從內存讀取
count
的值到 CPU 寄存器(如mov eax, [count]
); - 寄存器中的值加 1(如
inc eax
); - 將寄存器的值寫回內存(如
mov [count], eax
)。
如果操作不原子,可能出現以下 “交錯執行”:
- Thread A 執行步驟 1:讀取
count=0
到寄存器; - 此時 CPU 切換到 Thread B,Thread B 執行步驟 1-3:讀取
count=0
→ 加 1→ 寫回count=1
; - CPU 切回 Thread A,繼續執行步驟 2-3:寄存器值加 1(0→1)→ 寫回
count=1
;
最終 count
的結果是 1,但預期是 2。
原子操作的本質
“原子性” 需要硬件(CPU)提供底層支持,再配合軟件(操作系統、編程語言庫)封裝成易用的接口。
硬件層:CPU 的原子指令支持
不同架構的 CPU 會提供專門的 “原子操作指令”,確保單個指令的不可分割性:
- x86/x86_64 架構:通過
lock
前綴實現原子性(如lock cmpxchg
、lock inc
)。lock
前綴會讓 CPU 在執行指令期間 “鎖定系統總線”,阻止其他 CPU 核心同時訪問該內存地址,確保指令執行不被打斷; - ARM 架構:提供
ldrex
(原子加載)、strex
(原子存儲)等指令,通過 “獨占訪問內存” 機制實現原子性; - RISC-V 架構:通過
amoswap.w
、amoadd.w
等 “原子內存操作指令”(AMO 指令)實現原子性。
這些硬件指令是 “原子操作” 的基石 —— 軟件層面的原子接口(如 C++ 的 std::atomic
、Windows 的 _InterlockedXXX
)本質都是對這些 CPU 指令的封裝。
軟件層:原子操作的封裝與擴展
硬件指令通常只支持 “單個內存地址的簡單操作”(如加 1、比較交換),軟件會在此基礎上封裝更靈活的原子操作:
原子操作
原子的交換操作
_InterlockedExchange(state_lock, 2);
- 第一個參數
state_lock
:指向目標內存地址的指針(通常是一個共享變量,如LONG*
類型),即要被修改的 “狀態標記”。 - 第二個參數
2
:要寫入目標內存地址的 “新值”。 - 返回值:原子地將
state_lock
指向的內存值更新為2
,同時返回該內存地址在更新前的原始值
原子比較交換操作
_InterlockedCompareExchange(&lock_flag, 1, 0);
參數1:類型為 volatile LONG*
(指向 32 位有符號整數的指針),要操作的目標內存地址
參數2:類型為 LONG
(32 位有符號整數),比較成功后要寫入目標地址的值
參數3:類型為 LONG
,表示預期的目標地址當前值(即 “舊值”)。
返回值:返回值為 LONG
類型,即目標地址在操作執行前的原始值:
// 定義一個共享的“鎖標記”,0表示未占用,1表示已占用
LONG lock_flag = 0;// 線程A嘗試獲取鎖
LONG original = _InterlockedCompareExchange(&lock_flag, 1, 0);
if (original == 0) {//獲取到權限// 執行臨界區操作...// 操作完成后釋放鎖(如將lock_flag設回0)
} else {// 獲取鎖失敗(鎖已被其他線程占用)// 可選擇等待、重試或放棄
}
原子地將目標變量的值加
_InterlockedIncrement(dword_14002BFF0);
參數:dword_14002BFF0
是一個 LONG
類型(32 位)的共享變量(通常是全局或多線程可見的變量),表示要進行遞增操作的目標。
返回值:函數返回遞增后的新值(LONG
類型)。
原子減 1
_InterlockedDecrement(a1)
參數:類型為 volatile LONG*
(指向 32 位有符號整數的指針)
返回值:類型為 LONG
(32 位有符號整數),表示減 1 操作完成后的結果值(即 *a1 - 1
的結果)。
線程同步
SRW 鎖
SRW 鎖支持兩種獲取模式:
- 共享模式(Shared Mode):多個線程可同時獲取,適用于 “只讀操作” 場景(多個讀者可并行訪問資源)。
- 獨占模式(Exclusive Mode):僅允許一個線程獲取,適用于 “修改操作” 場景(寫者需獨占資源)。
初始化SRW
void InitializeSRWLock(PSRWLOCK SRWLock
);
參數:PSRWLOCK
是 SRWLOCK*
的類型別名,指向 SRWLOCK
結構體(輕量級讀寫鎖的核心數據結構)。
核心作用:初始化 SRW 鎖對象的內部狀態,初始化后的 SRW 鎖可通過以下函數實現讀寫分離的同步
- 讀操作:
AcquireSRWLockShared
(獲取共享鎖)和ReleaseSRWLockShared
(釋放共享鎖)。 - 寫操作:
AcquireSRWLockExclusive
(獲取獨占鎖)和ReleaseSRWLockExclusive
(釋放獨占鎖)。
讀操作
void AcquireSRWLockShared(PSRWLOCK SRWLock
);
參數 SRWLock
是指向 SRWLOCK
結構體的指針,讓線程以 “共享模式”(只讀模式)安全地獲取輕量級讀寫鎖(SRW Lock)。
void ReleaseSRWLockShared(PSRWLOCK SRWLock
);
參數 SRWLock
是指向 SRWLOCK
結構體的指針(PSRWLOCK
即 SRWLOCK*
),表示要釋放的輕量級讀寫鎖對象。
寫操作
void AcquireSRWLockExclusive(PSRWLOCK SRWLock
);
讓線程以 “獨占模式” 獲取 SRW 鎖,確保對共享資源的修改操作(寫操作)具有原子性
臨界區
進出臨界區
EnterCriticalSection(&stru_14002C030):
參數:類型為LPCRITICAL_SECTION
(即 CRITICAL_SECTION*
,指向臨界區結構體的指針)。
無返回值
作用:讓當前線程 “獲取臨界區的訪問權”
LeaveCriticalSection(&stru_14002C030);
作用:釋放臨界區
初始化臨界區
void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection
);
參數 lpCriticalSection
是指向 CRITICAL_SECTION
結構體的指針(LPCRITICAL_SECTION
是 CRITICAL_SECTION*
的類型別名),表示要初始化的臨界區對象。
異常:如果初始化失敗(通常是由于系統資源不足),函數會觸發一個異常(而非返回錯誤碼)。因此在實際使用中,可能需要配合異常處理(如 __try
/__except
)捕獲潛在錯誤。
作用:初始化臨界區對象的內部狀態,初始化后的臨界區可通過 EnterCriticalSection
(進入臨界區)和 LeaveCriticalSection
(離開臨界區)實現。
使用示例
// 定義臨界區對象(全局或棧上)
CRITICAL_SECTION CriticalSection;// 初始化臨界區(通常在程序啟動或模塊初始化時調用)
InitializeCriticalSection(&CriticalSection);// 多線程場景中使用
void ThreadFunc() {// 進入臨界區(獲取同步權限)EnterCriticalSection(&CriticalSection);// 執行需要同步的操作(如訪問共享資源)AccessSharedResource();// 離開臨界區(釋放同步權限)LeaveCriticalSection(&CriticalSection);
}// 程序退出前銷毀臨界區(釋放資源)
DeleteCriticalSection(&CriticalSection);
臨界區同步
TryEnterCriticalSection()
參數:LPCRITICAL_SECTION lpCriticalSection
指向 CRITICAL_SECTION
結構體的指針(與 EnterCriticalSection
相同),表示要嘗試進入的臨界區對象,需提前通過 InitializeCriticalSection
初始化。
返回值: TRUE
(非 0 值):表示成功進入臨界區,FALSE
(0):表示未能進入臨界區。
作用:非阻塞嘗試進入臨界區,與 EnterCriticalSection
的 “阻塞等待” 不同,TryEnterCriticalSection
的核心特點是 “嘗試進入,失敗立即返回”,適用于以下場景:
- 當線程只需 “短暫嘗試” 獲取臨界區,若失敗則執行其他任務(而非等待),避免線程阻塞。
- 實現 “超時等待” 邏輯(結合循環和
Sleep
,多次嘗試后放棄)。
臨界區狀態結構體
00000000 struct _RTL_CRITICAL_SECTION // sizeof=0x28
00000000 { // XREF: .data:CriticalSection/r
00000000 // .data:__rtl_critical_section/r
00000000 PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
00000008 LONG LockCount;
0000000C LONG RecursionCount;
00000010 HANDLE OwningThread;
00000018 HANDLE LockSemaphore;
00000020 ULONG_PTR SpinCount;
00000028 };
線程調度
CPU 時間片
SwitchToThread()
返回值:
- 返回
TRUE
(非 0 值):表示當前有其他 “就緒狀態” 的線程(屬于同一優先級或更高優先級)被調度執行; - 返回
FALSE
(0):表示當前沒有其他就緒線程可調度(即系統中只有當前線程可運行)。
作用:
- 當前線程主動放棄剩余的 CPU 時間片,讓操作系統調度器重新選擇一個就緒線程(通常是同優先級的其他線程)運行。
自旋等待
while ( 1 )
{v6 = _InterlockedCompareExchange(a1, 1, 0); // 原子比較交換:嘗試將a1從0改為1if ( !v6 ) // v6=0表示成功獲取權限(a1原本為0,已改為1)break;if ( v6 != 2 ) // 若a1當前為1(被其他線程占用),則主動讓出CPUSwitchToThread();if ( *a1 == 2 ) // 若等待期間a1變為2(終止),則返回0return 0LL;
}