8.1 原子訪問:Interlocked系列函數(shù)
原子訪問: 一個線程在訪問某個資源的同時能夠保證沒有其他線程會在同一時刻訪問同一資源。
InterlockedExchanAdd 和InterlockedExchangeAdd64
LONG InterlockedExchangeAdd(
PLONG volatile plAddend,
LONG lIncrement);
LONGLONG InterlockedExchangeAdd64(
PLONGLONG volatile pllAddend,
LONG llIncrement); // 該函數(shù)針對LONGLONG類型的
【注意】使用這些函數(shù)的時候需要保證傳給這些函數(shù)的變量地址是對其的,否則這些函數(shù)可能會失敗。
C運行庫提供了一個_aligned_malloc函數(shù),可以用這個函數(shù)來分配一塊對其的內(nèi)存。
void* _aligned_malloc(size_t size, size_t alignment);
// alignment表示要對齊到的字節(jié)邊界。
下面為其他的Interlocked函數(shù):
LONG InterlockedExchange(
PLONG volatile plTarget,
LONG lValue);
LONGLONG InterlockedExchange64(
PLONGLONG volatile plTarget,
LONG lValue); // 該函數(shù)針對LONGLONG類型的
PVOID InterlockedExchangePointer(
PVOID* volatile ppvTarget,
PVOID pvValue);
PLONG InterlockedCompareExchanged(
PLONG plDestination,
LONG lExchange,
LONG lComparand);
PLONG InterlockedCompareExchangedPointer(
PVOID* ppvDestination,
PVOID pvExchange,
PVOID pvCompared);
InterlockedExchange和InterlockedExchangePointer會把第一個參數(shù)指向的內(nèi)存地址的當(dāng)前值,以原子方式替換為第二個參數(shù)指定的值。
如果線程只需要讀取一個值的內(nèi)容,而這個值始終是通過Interlocked函數(shù)此前該的,那么讀到的值不會有問題。
當(dāng)多個進程需要對訪問一個共享內(nèi)存段(比如內(nèi)存映射文件) 中的值進行同步時, 也可以使用Interlocked函數(shù)。
| 函數(shù) | 描述 |
|---|---|
| InitializeSListHead | 創(chuàng)建一個空棧 |
| InterlockedPushEntrySList | 在棧頂添加一個元素 |
| InterlockedPopEntrySList | 一處位于棧頂?shù)脑夭⑺祷?/td> |
| InterlockedFlushSList | 清空棧 |
| QueryDepthSList | 返回棧中元素的數(shù)量 |
表8-1 Interlocked單向鏈表函數(shù)
| 函數(shù) | 描述 |
|---|---|
| InitializeSListHead | 創(chuàng)建一個空棧 |
| InterlockedPushEntrySList | 在棧頂添加一個元素 |
| InterlockedPopEntrySList | 一處位于棧頂?shù)脑夭⑺祷?/td> |
| InterlockedFlushSList | 清空棧 |
| QueryDepthSList | 返回棧中元素的數(shù)量 |
8.2 高速緩存行
當(dāng)CPU從內(nèi)存行中讀取一個字節(jié)的時候,是取回一個高速緩存行。
既不應(yīng)該使用旋轉(zhuǎn)鎖,也不應(yīng)該進行輪循,而應(yīng)該調(diào)用函數(shù)吧線程切換為等待狀態(tài),知道線程想要訪問的資源可供使用為止。
volatile限定符告訴編譯器不要對這個變量進行任何形式的優(yōu)化,而是始終從變量在內(nèi)存中的位置讀取變量的值。
給一個結(jié)構(gòu)加volatile限定符等于給結(jié)構(gòu)中所有的成員加volatile限定符,這樣可以確保任何一個成員始終都是從內(nèi)存中讀取的。
8.4 關(guān)鍵段
關(guān)鍵段是一小段代碼,它在執(zhí)行之前需要獨占對一些共享資源的訪問權(quán)。
const int COUNT = 10;
int g_nSum = 0;
CRITICAL_SECTION g_cs;
DWORD WINAPI FirstThread(PVOID pvParam)
{
EnterCriticalSection(&g_cs); //傳入的是g_cs的地址
g_nSum = 0;
for(int n = 1; n <= COUNT; n++)
g_nSum += n;
LeaveCriticalSection(&g_cs);
return g_nSum;
}
DWORD WINAPI SecondThread(PVOID pvParam)
{
EnterCriticalSection(&g_cs);
g_nSum = 0;
for(int n = 1; n <= COUNT; n++)
g_nSum += n;
LeaveCriticalSection(&g_cs);
return g_nSum;
}
當(dāng)有一個資源讓多個線程訪問的時候,應(yīng)該創(chuàng)建一個CRITICAL_SECTION結(jié)構(gòu)。
【注意】在調(diào)用EnterCriticalSection和LeaveCriticalSection的時候,傳入的是CRITICAL_SECTION結(jié)構(gòu)的地址。
關(guān)鍵段最大的缺點是,無法用在多個進程之間的線程同步
使用CRITICAL_SECTION所需的兩個必要的條件:
- 所有想要訪問資源的線程必須知道該結(jié)構(gòu)的地址
- 在任何線程試圖訪問保護資源之前,必須對該結(jié)構(gòu)進行初始化
VOID InitializeCritcalSection(PCRITICAL_SECTION pcs);
不再需要訪問共享資源的時候,調(diào)用:
VOID DeleteCritcalSection(PCRITICAL_SECTION pcs);
TryEnterCriticalSection可以用來代替EnterCriticalSection
BOOL TryEnterCriticalSection(PCRITICAL_SECTION pcs);
其不會讓調(diào)用線程進入等待狀態(tài), 其會通過返回值表示調(diào)用的線程是否獲準(zhǔn)訪問資源。
關(guān)鍵段和旋轉(zhuǎn)鎖
為了在使用關(guān)鍵段的同時使用旋轉(zhuǎn)鎖,必須調(diào)用以下的函數(shù)來初始化關(guān)鍵段:
BOOL IniticalizeCriticalSectionAndSpinCount(
PCRITICAL_SECTION pcs,
DWORD dwSpinCount); //旋轉(zhuǎn)鎖循環(huán)的次數(shù)
【注意】如果主機只有一個處理器,函數(shù)會忽略dwSpinCount參數(shù)。
8.5 Slim讀/寫鎖
SRWLock的功能:
- 區(qū)分讀取資源值的線程(讀取者線程)和更新資源值的線程(寫入者線程)
- 讀取者線程在同一時刻可以訪問共享資源
- 寫入者線程獨占對資源的訪問權(quán),任何其他線程都不允許訪問資源。
使用步驟:
VOID InitializeSRWLock(PSRWLOCK SRWLock);
// 分配一個SRWLock結(jié)構(gòu)并初始化
VOID AcquireSRWlockExclusive(PSRWLOCK SRWLock);
//參數(shù)為SRWLock對象地址,嘗試獲得對被保護的資源的獨占訪問權(quán)
VOID ReleaseSRWLockLockExclusive(PSRWLOCK SRWLock);
總結(jié)
在應(yīng)用程序中若想獲得最佳性能,首先應(yīng)該嘗試不要共享數(shù)據(jù),然后依次使用volatile讀取,volatile寫入,Interlocked API,SRWLock以及關(guān)鍵段。當(dāng)這些條件不滿足要求的時候,再使用內(nèi)核對象。
8.6 條件變量
SleepConditionVariableCS和SleepConditionVariableSRW
BOOL SleepConditonVariable(
PCONDITION_VARIABLE pConditionVariable,
PCRITICAL_SECTION pCriticalSection,
DWORD dwMilliseconds);
BOOL SleepConditionVariableSRW(
PCONDITION_VARIABLE pConditionVariable,
PSRWLOCK pSRWLock,
DWROD dwMilliseconds,
ULONG Flags);
參數(shù):
- pConditionVariable: 指向已初始化的條件變量,調(diào)用線程正在等待該條件變量。
- pCriticalSection:指向關(guān)鍵段或者SRWLock的指針
- dwMilliseconds:線程等待條件變量被觸發(fā)的時間。
- Flags:線程已何種方式得到鎖:0 ,針對寫入者線程,表示獨占對資源的訪問;CONDITION_VARIABLE_LOCKMODE_SHARED,針對讀寫者線程,共享對資源的訪問。
當(dāng)另一個線程檢測到相應(yīng)的條件已經(jīng)滿足的時候,它會調(diào)用WakeConditionVariable或WakeAllConditionVariable
這樣阻塞在Sleep*函數(shù)中的線程就會被喚醒。
VOID WakeConditionVariable(
PCONDITION_VARIABLE ConditionVariable);
VOID WakeAllConditionVariable(
PCONDITION_VARIABLE ConditionVariable);
一些有用的技巧和竅門
- 以原子方式操作一組對象時使用一個鎖
- 同時訪問多個邏輯資源
在代碼中的任何地方以完全相同的順序來獲得資源的鎖。
DWORD WINAPI ThreadFunc(PVOID pvParam)
{
EnterCritcalSection(&g_csResource1);
EnterCritcalSection(&g_csResource2);
.....
LeaveCritcalSection(&g_csResource1);
LeaveCritcalSection(&g_csResource2);
reutrn 0;
}
DWORD WINAPI OtherThreadFunc(PVOID pvParam)
{
EnterCritcalSection(&g_csResource2);
EnterCritcalSection(&g_csResource1);
.....
LeaveCritcalSection(&g_csResource1);
LeaveCritcalSection(&g_csResource2);
reutrn 0;
}
//這兩個函數(shù)的編寫方式,有可能造成死鎖。
- 不要太長時間占用鎖