使用臨界區物件(CriticalSection)需要注意的一些事情
使用臨界區物件(CriticalSection)需要注意的一些事情
2013年10月28日 ⁄ 綜合 ⁄ 共 2591字 ⁄ 字號 小 中 大 ⁄ 評論關閉
1. 臨界區物件不是核心物件,因此不能繼承,不能跨程序,也不能用waitfor什麼的函式來限定時間等待。這個很好理解,你想想WaitFor要求傳一個控制代碼,而臨界區物件的型別都不是控制代碼,也不能用CloseHandle來關閉,怎麼可能會能讓WaitForXXX搞了。
2. 臨界區物件使用前必須初始化,不初始化會崩潰,這是我的親歷。
3. 執行緒進入臨界區之前會先自旋那麼幾次,所有自旋鎖都失敗了之後會建立一個核心物件然後等待這個核心從而進入到核心模式。
4. Enter和Leave必須匹配,每Enter一次,就得Leave一次,這又是可惡的計數機制。參見下面的程式碼:
typedef struct _RTL_CRITICAL_SECTION { PRTL_CRITICAL_SECTION_DEBUG DebugInfo; // // The following three fields control entering and exiting the critical // section for the resource // LONG LockCount; LONG RecursionCount; HANDLE OwningThread; // from the thread's ClientId->UniqueThread HANDLE LockSemaphore; ULONG_PTR SpinCount; // force size on 64-bit systems when packed } RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
這是臨界區物件的定義,看見
RecursionCount
這個物件了吧,你覺得它能幹點啥?同時在這裡你還能看到一個訊號量的核心物件,還有一個自旋數量。這些玩意印證了上面的話。如果你同一個執行緒Leave之前Enter了兩次,必須呼叫兩個Leave,不然這個臨界區物件依然會阻塞別的執行緒。再不明白就去看我前面有關掛起執行緒的那個博文。
5. 由於進入臨界去是無限等待的,因此你有時間肯定希望有種方法能夠檢視一下臨界區是否可用,不可用則希望執行緒立刻去做其它的事情。這時候,你就需要一個TryEnterCriticalSectionAPI,這玩意很好理解,你踹一腳臨界區,如果能進去就進去,不能進去這個API立刻以False返回,你就可以安排執行緒去做其它的事情。注意:你一腳踹進去了之後完事了記得要離開(LeaveCriticalSection)。
6. 前面說了,臨界區真正用核心物件掛起執行緒之前會自旋好幾次,因此你看物件裡就有一個自旋鎖的計數。你可以改這個自旋鎖的數量。當然我不是說讓你直接修改物件的成員變數!你可以在初始化的時候指定自旋鎖的數量,用這個API:InitializeCriticalSectionAndSpinCount。在這裡小說一下臨界區為什麼會自旋。因為程式從使用者態轉到核心模式需要昂貴的開銷(大概數百個CPU週期),很多情況下,A執行緒還沒完成從使用者態轉到核心態的操作呢,B執行緒就已經釋放資源了。於是臨界區就先隔一段時間自旋一次,直到所有自旋次數都耗盡,就建立個核心物件然後掛起執行緒。但是,如果您的機器只有一個CPU,那麼這個自旋次數就沒用了,作業系統直接會無視它。原因如下:你自旋著呢,操作B執行緒釋放不了資源,於是你還不如直接切入等待狀態讓B來釋放資源。動態更改自旋數量請使用SetCriticalSectionSpinCount,別做直接更改物件成員變數的二事!
7. 最後,初始化臨界區和進入臨界區的時候都有可能會遇到異常狀況,比如初始化的時候需要申請一些記憶體來儲存DEBUG的資訊(參見上面程式碼的第一個成員變數),如果記憶體不夠,初始化就崩了。進入臨界區的時候可能需要新建一個核心物件,如果記憶體不夠,也崩了。解決這個問題的方法有兩種
- 結構化異常處理
- 初始化的時候使用InitializeCriticalSectionAndSpinCount。這個API有返回值,如果它申請DEBUG資訊失敗,返回FALSE,另外,剛才提到了這個API可以指定自旋鎖的自旋次數。這個自旋次數的範圍並非是用0到0xFFFF
FFFF而是0--->0x00FF FFFF,因此你可以設定高位為1指示初始化的時候直接建立核心物件。如果建立失敗,這個函式也會呼叫失敗。當然了,一上來就搞一個核心物件似乎有點浪費記憶體,但是這樣能夠保證進入臨界區不會失敗!但是吧,你需要注意,設定高位來保證核心物件的建立只能在2000上玩。MSDN上有說明,不信你看: - Windows 2000: If the high-order bit is set, the function pre-allocates the event used by theEnterCriticalSection
function. Pre-allocation guarantees that entering or leaving the critical section will not raise an exception in low memory conditions. Do not set this bit if you are creating a large number of critical section objects, because it consumes a significant amount
of nonpaged pool. Note that this event is allocated on demand starting with Windows XP and the high-order bit is ignored.
最後是一些實驗:
我們看看用InitializeCriticalSection初始化一個臨界區物件後,這些成員變數(除去DEBUG外)都是什麼樣子。
我們Enter一下,看看會變成什麼樣子
我們再讓其它執行緒也Enter一下看看
可見,新建了一個核心物件。
我們現在讓主執行緒退出臨界區
對照執行緒控制代碼我們可以看出第二個執行緒獲得了臨界區物件。
我們再讓第二個執行緒退出臨界區。
臨界區除去核心物件外回到了原始狀態。
實驗2:我們讓臨界區物件在同一執行緒內相繼被進入兩次
::EnterCriticalSection(&g_cs); ::EnterCriticalSection(&g_cs);
可見,計數增加了一個,變成了,因此你得leave兩次才能開鎖