CCriticalSection的使用與探究

CCriticalSection是對關鍵段CRITICAL_SECTION的封裝。

關鍵段(critival section)是一小段代碼,他在執行之前需要獨佔對一些共享資源的訪問權。這種方式可以讓多行代碼以“原子方式”來對資源進行操控。這裏的“原子方式”,指的是代碼知道除了當前線程之外沒有其他任何線程會同時訪問該資源。當然,系統仍然可以暫停當前線程去調度其他線程。但是,在當前線程離開關鍵段之前,系統是不會去調度任何想要訪問同一資源的其他線程的。

例如:如果兩個線程同時訪問一個鏈表,一個線程可能會在另一個線程在鏈表中搜尋一個元素的同時向鏈表中添加一個元素,將導致搜索結果不正確;還有可能兩個線程同時向鏈表中添加元素,這種情況會變的更加混亂;甚至一個線程搜索的時候,另一個線程刪除了鏈表節點,將直接導致程序崩潰。

解決這個問題,我們可以現在代碼中定義一個CRITICAL_SECTION數據結構m_sect,然後把任何需要訪問共享資源的代碼放在EnterCriticalSection和LeaveCriticalSection之間。


  1. EnterCriticalSection(&m_sect);  

  2. // 共享資源的代碼段....


  3. LeaveCriticalSection(&m_sect);  


一個 CRITICAL_SECTION結構就像是飛機上的一個衛生間,而馬桶則是我們想要保護的資源(用EnterCriticalSection和LeaveCriticalSection組成的圍牆包圍住“馬桶”)。由於衛生間很小,因此在同一時刻只允許一個人在衛生間內使用馬桶(在同一時刻只允許一個線程在關鍵段中使用被保護資源)。

如果有多個總是應該在一起使用的資源,那麼我們可以把他們放在同一個“衛生間”中:只需要創建一個CRITICAL_SECTION結構來保護所有這些資源。

關於關鍵段,需要掌握以下幾點:

1、任何要訪問共享資源的代碼,都必須包含在EnterCriticalSection和LeaveCriticalSection之間。如果忘了哪怕是一個地方,共享資源就有可能被破壞。忘記調用EnterCriticalSection和LeaveCriticalSection,就好像是未經許可就強制進入衛生間一樣,線程強行進入並對資源進行操控。只要有一個線程有這種粗暴的行爲,資源就會被破壞。

2、關鍵段CRITICAL_SECTION是個未公開的結構,因爲Microsoft認爲開發人員不需要理解這個結構的細節。對我們來說,不需要知道這個結構中的成員變量,我們絕對不應該在編寫代碼的時候用到他的成員。

3、爲了對CRITICAL_SECTION結構進行操控,我們必須調用Windows API函數,傳入結構的地址。(注意是地址!)也就是說,如果該CRITICAL_SECTION結構生命週期沒有結束,那麼可以將該結構地址通過自己喜歡的任何方式傳給任何線程。

4、在任何線程試圖訪問被保護的資源之前,必須對CRITICAL_SECTION結構的內部成員進程初始化。我們不知道內部成員,但可以調用Windows函數實現:VOID WINAPI InitializeCriticalSection(__out LPCRITICAL_SECTION lpCriticalSection);

5、當線程不再需要訪問共享資源的時候,應調用下面的函數來清理該結構:VOID WINAPI DeleteCriticalSection(__inout LPCRITICAL_SECTION lpCriticalSection);

6、其實CRITICAL_SECTION並不知道什麼是共享資源,也不會智能保護共享資源。其根本是,同一個時刻如果有多個線程調用EnterCriticalSection的時候,只有一個線程返回,其餘線程則暫停執行,等待前面線程調用LeaveCriticalSection之後再執行。

7、可以看出,進入關鍵段是沒有超時設定的,好像永遠不會超時。實際上,對EnterCriticalSection的調用也會超時並引發異常。超時的時間長度由下面這個註冊表子項中包含的CriticalSectionTimeout值決定:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager

這個值以秒爲單位,他的默認值爲2592000秒,大約30天。

8、同一個線程可以隨便進入用一個關鍵段N次,也就是說同一個線程調用EnterCriticalSection無論幾次都會返回。不同線程是否能夠進入關鍵段,要看EnterCriticalSection的參數(CRITICAL_SECTION結構的地址)之前是否有線程進入過。要記住:飛機上的衛生間有多個,你可以隨便進入無人的衛生間,不能進入有人的衛生間。

弄明白了CRITICAL_SECTION之後,使用CCriticalSection非常方便,如虎添翼。看代碼:

//頭文件

  1. class CCriticalSection : public CSyncObjet  

  2. {...  

  3. public:  

  4.    CRITICAL_SECTION m_sect;  

  5. public:  

  6. BOOL Unlock();  

  7. BOOL Lock();  

  8. BOOL Lock(DWORD dwTimeout);  

  9. ...  

  10. }  


// 構造函數

  1. CCriticalSection::CCriticalSection() : CSyncObject(NULL)  

  2. {        

  3. HRESULT hr = S_OK;  

  4. if (!InitializeCriticalSectionAndSpinCount(&m_sect, 0))//可以理解爲InitializeCriticalSection,爲了效率,加了一個旋轉鎖。

  5.    {  

  6.        hr =  HRESULT_FROM_WIN32(GetLastError());  

  7.    }  


  8. if (FAILED(hr))  

  9.    {  

  10.        AtlThrow(hr);  

  11.    }        

  12. }  


//進入關鍵段

  1. BOOL CCriticalSection::Lock()  

  2. {    

  3.    ::EnterCriticalSection(&m_sect);  


  4. return TRUE;  

  5. }  



// 離開關鍵段

  1. BOOL CCriticalSection::Unlock()  

  2. {  

  3.    ::LeaveCriticalSection(&m_sect);  


  4. return TRUE;  

  5. }  


// 析構

  1. CCriticalSection::~CCriticalSection()  

  2. {  

  3.    ::DeleteCriticalSection(&m_sect);  

  4. }  


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章