CRITICAL_SECTION用法案例詳解

      很多人對CRITICAL_SECTION的理解是錯誤的,認為CRITICAL_SECTION是鎖定瞭資源,其實,CRITICAL_SECTION是不能夠“鎖定”資源的,它能夠完成的功能,是同步不同線程的代碼段。簡單說,當一個線程執行瞭EnterCritialSection之後,cs裡面的信息便被修改,以指明哪一個線程占用瞭它。而此時,並沒有任何資源被“鎖定”。不管什麼資源,其它線程都還是可以訪問的(當然,執行的結果可能是錯誤的)。隻不過,在這個線程尚未執行LeaveCriticalSection之前,其它線程碰到EnterCritialSection語句的話,就會處於等待狀態,相當於線程被掛起瞭。 這種情況下,就起到瞭保護共享資源的作用。

      也正由於CRITICAL_SECTION是這樣發揮作用的,所以,必須把每一個線程中訪問共享資源的語句都放在EnterCritialSection和LeaveCriticalSection之間。這是初學者很容易忽略的地方。

      當然,上面說的都是對於同一個CRITICAL_SECTION而言的。 如果用到兩個CRITICAL_SECTION,比如說:

第一個線程已經執行瞭EnterCriticalSection(&cs)並且還沒有執行LeaveCriticalSection(&cs),這時另一個線程想要執行EnterCriticalSection(&cs2),這種情況是可以的(除非cs2已經被第三個線程搶先占用瞭)。這也就是多個CRITICAL_SECTION實現同步的思想。

       比如說我們定義瞭一個共享資源dwTime[100],兩個線程ThreadFuncA和ThreadFuncB都對它進行讀寫操作。當我們想要保證 dwTime[100]的操作完整性,即不希望寫到一半的數據被另一個線程讀取,那麼用CRITICAL_SECTION來進行線程同步如下:

      第一個線程函數:

DWORD WINAPI ThreadFuncA(LPVOID lp)
{
            EnterCriticalSection(&cs);
            ...
            //   操作dwTime
            ...
            LeaveCriticalSection(&cs);
            return   0;
}

       寫出這個函數之後,很多初學者都會錯誤地以為,此時cs對dwTime進行瞭鎖定操作,dwTime處於cs的保護之中。一個“自然而然”的想法就是——cs和dwTime一一對應上瞭。這麼想,就大錯特錯瞭。dwTime並沒有和任何東西對應,它仍然是任何其它線程都可以訪問的。
如果你像如下的方式來寫第二個線程,那麼就會有問題:

DWORD   WINAPI   ThreadFuncB(LPVOID   lp)
{
            ...
            //   操作dwTime
            ...
            return   0;
}

      當線程ThreadFuncA執行瞭EnterCriticalSection(&cs),並開始操作dwTime[100]的時候,線程ThreadFuncB可能隨時醒過來,也開始操作dwTime[100],這樣,dwTime[100]中的數據就被破壞瞭。

      為瞭讓 CRITICAL_SECTION發揮作用,我們必須在訪問dwTime的任何一個地方都加上 EnterCriticalSection(&cs)和LeaveCriticalSection(&cs)語句。所以,必須按照下面的方式來寫第二個線程函數:

DWORD   WINAPI   ThreadFuncB(LPVOID   lp)
{
            EnterCriticalSection(&cs);
            ...
            //   操作dwTime
            ...
            LeaveCriticalSection(&cs);
            return   0;
}

      這樣,當線程ThreadFuncB醒過來時,它遇到的第一個語句是EnterCriticalSection(&cs),這個語句將對cs變量進行訪問。如果這個時候第一個線程仍然在操作dwTime[100],cs變量中包含的值將告訴第二個線程,已有其它線程占用瞭cs。因此,第二個線程的 EnterCriticalSection(&cs)語句將不會返回,而處於掛起等待狀態。直到第一個線程執行瞭 LeaveCriticalSection(&cs),第二個線程的EnterCriticalSection(&cs)語句才會返回,並且繼續執行下面的操作。

      這個過程實際上是通過限制有且隻有一個函數進入CriticalSection變量來實現代碼段同步的。簡單地說,對於同一個CRITICAL_SECTION,當一個線程執行瞭EnterCriticalSection而沒有執行 LeaveCriticalSection的時候,其它任何一個線程都無法完全執行EnterCriticalSection而不得不處於等待狀態。

      再次強調一次,沒有任何資源被“鎖定”,CRITICAL_SECTION這個東東不是針對於資源的,而是針對於不同線程間的代碼段的!我們能夠用它來進行所謂資源的“鎖定”,其實是因為我們在任何訪問共享資源的地方都加入瞭EnterCriticalSection和 LeaveCriticalSection語句,使得同一時間隻能夠有一個線程的代碼段訪問到該共享資源而已(其它想訪問該資源的代如果是兩個CRITICAL_SECTION,就以此類推。碼段不得不等待)。
如果是兩個CRITICAL_SECTION,就以此類推。

再舉個極端的例子,可以幫助你理解CRITICAL_SECTION這個東東:
第一個線程函數:

DWORD   WINAPI   ThreadFuncA(LPVOID   lp)
{
            EnterCriticalSection(&cs);
            for(int   i=0;i <1000;i++)
                        Sleep(1000);
            LeaveCriticalSection(&cs);
            return   0;
}

第二個線程函數:

DWORD   WINAPI   ThreadFuncB(LPVOID   lp)
{
            EnterCriticalSection(&cs);
            index=2;
            LeaveCriticalSection(&cs);
            return   0;
}

      這種情況下,第一個線程中間總共Sleep瞭1000秒鐘!它顯然沒有對任何資源進行什麼“有意識”的保護;而第二個線程是要訪問資源index的,但是由於第一個線程占用瞭cs,一直沒有Leave,而導致第二個線程不得不等上1000秒鐘……
第二個線程,真是可憐啊!
這個應該很說明問題瞭,你會看到第二個線程在1000秒鐘之後開始執行index=2這個語句。也就是說,CRITICAL_SECTION其實並不理會你關心的具體共享資源,它隻按照自己的規律辦事~

到此這篇關於CRITICAL_SECTION用法案例詳解的文章就介紹到這瞭,更多相關CRITICAL_SECTION用法內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: