秒杀多线程第五篇 经典线程同步 关键段CS
上一篇《秒殺多線程第四篇 一個經典的多線程同步問題》提出了一個經典的多線程同步互斥問題,本篇將用關鍵段CRITICAL_SECTION來嘗試解決這個問題。
本文首先介紹下如何使用關鍵段,然后再深層次的分析下關鍵段的實現機制與原理。
關鍵段CRITICAL_SECTION一共就四個函數,使用很是方便。下面是這四個函數的原型和使用說明。
?
函數功能:初始化
函數原型:
void?InitializeCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
函數說明:定義關鍵段變量后必須先初始化。
?
函數功能:銷毀
函數原型:
void?DeleteCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
函數說明:用完之后記得銷毀。
?
函數功能:進入關鍵區域
函數原型:
void?EnterCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
函數說明:系統保證各線程互斥的進入關鍵區域。
?
函數功能:離開關關鍵區域
函數原型:
void?LeaveCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
?
然后在經典多線程問題中設置二個關鍵區域。一個是主線程在遞增子線程序號時,另一個是各子線程互斥的訪問輸出全局資源時。詳見代碼:
[cpp]?view plaincopy運行結果如下圖:
可以看出來,各子線程已經可以互斥的訪問與輸出全局資源了,但主線程與子線程之間的同步還是有點問題。
???????這是為什么了?
要解開這個迷,最直接的方法就是先在程序中加上斷點來查看程序的運行流程。斷點處置示意如下:
然后按F5進行調試,正常來說這兩個斷點應該是依次輪流執行,但實際調試時卻發現不是如此,主線程可以多次通過第一個斷點即
???????EnterCriticalSection(&g_csThreadParameter);//進入子線程序號關鍵區域
這一語句。這說明主線程能多次進入這個關鍵區域!找到主線程和子線程沒能同步的原因后,下面就來分析下原因的原因吧^_^
?
先找到關鍵段CRITICAL_SECTION的定義吧,它在WinBase.h中被定義成RTL_CRITICAL_SECTION。而RTL_CRITICAL_SECTION在WinNT.h中聲明,它其實是個結構體:
typedef struct?_RTL_CRITICAL_SECTION?{
????PRTL_CRITICAL_SECTION_DEBUGDebugInfo;
????LONGLockCount;
????LONGRecursionCount;
????HANDLEOwningThread;?// from the thread's ClientId->UniqueThread
????HANDLELockSemaphore;
????DWORDSpinCount;
}?RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
各個參數的解釋如下:
第一個參數:PRTL_CRITICAL_SECTION_DEBUGDebugInfo;
調試用的。
?
第二個參數:LONGLockCount;
初始化為-1,n表示有n個線程在等待。
?
第三個參數:LONGRecursionCount;??
表示該關鍵段的擁有線程對此資源獲得關鍵段次數,初為0。
?
第四個參數:HANDLEOwningThread; ?
即擁有該關鍵段的線程句柄,微軟對其注釋為——from the thread's ClientId->UniqueThread
?
第五個參數:HANDLELockSemaphore;
實際上是一個自復位事件。
?
第六個參數:DWORDSpinCount;????
旋轉鎖的設置,單CPU下忽略
?
由這個結構可以知道關鍵段會記錄擁有該關鍵段的線程句柄即關鍵段是有“線程所有權”概念的。事實上它會用第四個參數OwningThread來記錄獲準進入關鍵區域的線程句柄,如果這個線程再次進入,EnterCriticalSection()會更新第三個參數RecursionCount以記錄該線程進入的次數并立即返回讓該線程進入。其它線程調用EnterCriticalSection()則會被切換到等待狀態,一旦擁有線程所有權的線程調用LeaveCriticalSection()使其進入的次數為0時,系統會自動更新關鍵段并將等待中的線程換回可調度狀態。
因此可以將關鍵段比作旅館的房卡,調用EnterCriticalSection()即申請房卡,得到房卡后自己當然是可以多次進出房間的,在你調用LeaveCriticalSection()交出房卡之前,別人自然是無法進入該房間。
回到這個經典線程同步問題上,主線程正是由于擁有“線程所有權”即房卡,所以它可以重復進入關鍵代碼區域從而導致子線程在接收參數之前主線程就已經修改了這個參數。所以關鍵段可以用于線程間的互斥,但不可以用于同步。
?
另外,由于將線程切換到等待狀態的開銷較大,因此為了提高關鍵段的性能,Microsoft將旋轉鎖合并到關鍵段中,這樣EnterCriticalSection()會先用一個旋轉鎖不斷循環,嘗試一段時間才會將線程切換到等待狀態。下面是配合了旋轉鎖的關鍵段初始化函數
函數功能:初始化關鍵段并設置旋轉次數
函數原型:
BOOLInitializeCriticalSectionAndSpinCount(
??LPCRITICAL_SECTIONlpCriticalSection,
??DWORDdwSpinCount);
函數說明:旋轉次數一般設置為4000。
?
函數功能:修改關鍵段的旋轉次數
函數原型:
DWORDSetCriticalSectionSpinCount(
??LPCRITICAL_SECTIONlpCriticalSection,
??DWORDdwSpinCount);
?
《Windows核心編程》第五版的第八章推薦在使用關鍵段的時候同時使用旋轉鎖,這樣有助于提高性能。值得注意的是如果主機只有一個處理器,那么設置旋轉鎖是無效的。無法進入關鍵區域的線程總會被系統將其切換到等待狀態。
?
?
最后總結下關鍵段:
1.關鍵段共初始化化、銷毀、進入和離開關鍵區域四個函數。
2.關鍵段可以解決線程的互斥問題,但因為具有“線程所有權”,所以無法解決同步問題。
3.推薦關鍵段與旋轉鎖配合使用。
?
下一篇《秒殺多線程第六篇 經典線程同步 事件Event》將介紹使用事件Event來解決這個經典線程同步問題。
?
轉載請標明出處,原文地址:http://blog.csdn.net/morewindows/article/details/7442639
如果覺得本文對您有幫助,請點擊‘頂’支持一下,您的支持是我寫作最大的動力,謝謝。
總結
以上是生活随笔為你收集整理的秒杀多线程第五篇 经典线程同步 关键段CS的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 秒杀多线程第三篇 原子操作 Interl
- 下一篇: 秒杀多线程第七篇 经典线程同步 互斥量M