第八章 用戶模式下的線程同步

8.1 原子訪問:Interlocked系列函數(shù)

原子訪問: 一個線程在訪問某個資源的同時能夠保證沒有其他線程會在同一時刻訪問同一資源。
InterlockedExchanAddInterlockedExchangeAdd64

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);

InterlockedExchangeInterlockedExchangePointer會把第一個參數(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)用EnterCriticalSectionLeaveCriticalSection的時候,傳入的是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 條件變量

SleepConditionVariableCSSleepConditionVariableSRW

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)用WakeConditionVariableWakeAllConditionVariable
這樣阻塞在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ù)的編寫方式,有可能造成死鎖。
  • 不要太長時間占用鎖
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 從三月份找實習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時芥藍閱讀 42,767評論 11 349
  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,887評論 0 11
  • 引用自多線程編程指南應(yīng)用程序里面多個線程的存在引發(fā)了多個執(zhí)行線程安全訪問資源的潛在問題。兩個線程同時修改同一資源有...
    Mitchell閱讀 2,108評論 1 7
  • 很久不曾提筆寫那些斷章取義、濃愁不散的句子,很久了,久到自己都忘了時日,如果我說你改變了我、是你讓我在浮夸的世...
    口袋姑娘55閱讀 204評論 0 0
  • 《浮生樓》目錄 【前幕】 訴愿間里。 素紗籠的昏黃燭光下,丹羽輕依在太師椅上,白玉般的纖手支著腦袋,秋水眸子看著眼...
    梁暔閱讀 659評論 0 9

友情鏈接更多精彩內(nèi)容