同步---CriticalSection,Mutex,Event,Semaphores2011-09-22 13:49:46| 分類: Win32---API | 標簽:線程同步 |字號 訂閱 關于線程的同步對象可分為內(nèi)核對象與非內(nèi)核對象,最大區(qū)別在于內(nèi)核對象能跨越進程,而非內(nèi)核對象不能跨越進程,只能同步單個進程中的線程。 內(nèi)核對象:(非內(nèi)核對象: CriticalSection) 1. 進程,Processe 2. 線程,Threads 3. 文件,F(xiàn)iles 4. 控制臺輸入,Console input 5. 文件變化通知,F(xiàn)ile change notifications 6. 互斥量,Mutexes 7. 信號量,Semaphores 8. 事件Events 9. 可等的計時器Waitable timers 10.Jobs 每一個上面這些類型的對象都可以處于兩種狀態(tài)之一:有信號(signaled)和無信號(nonsignaled)??捎镁褪怯行盘枲顟B(tài),被占用就是無信號狀態(tài)。比如進程和線程在終結時其內(nèi)核對象變?yōu)橛行盘枺谒鼈兲幱趧?chuàng)建和正在運行時,其內(nèi)核對象是無信號的。 內(nèi)核對象同步應用: 1.某線程獲得某進程的內(nèi)核對象句柄時,可以改變進程優(yōu)先級、獲得進程的退出碼;使本線程與某進程的終結取得同步等。 2.當獲得某線程的內(nèi)核對象句柄時,可以改變該線程運行狀態(tài)、與該線程的終結取得同步等。 3.當獲得文件句柄時,本線程可與某一個異步文件的I/O操作獲得同步等。 4.控制臺輸入對象可用來使線程在有輸入進入時被喚醒以執(zhí)行相關任務等。 5.其它內(nèi)核對象―――文件改變通知、互斥量、信號量、事件、可等計時器等―――都只是為了同步對象而存在
下面詳細介紹一下常用的同步對象: CritiaclSection: 臨界區(qū)是保證在某一時刻只有一個線程能訪問數(shù)據(jù)的簡便辦法。在任意時刻只允許一個線程對共享資源進行訪問,如果有多個線程試圖同時訪問臨界區(qū),那么在有一個線程進入后其他所有試圖訪問此臨界區(qū)的線程將被掛起,并一直持續(xù)到進入臨界區(qū)的線程離開。臨界區(qū)在被釋放后,其他線程可以繼續(xù)搶占,并以此達到用原子方式操作共享資源的目的。 在所有同步對象中,臨界區(qū)是最容易使用的,但它只能用于同步單個進程中的線程,并且不是內(nèi)核對象,它不由操作系統(tǒng)的低級部件管理,而且不能使用句柄來操縱,由于不是內(nèi)核對象,使得它作為一種輕量級的同步機制,同步速度比較快。 使用步驟: 1.在進程中創(chuàng)建一個臨界區(qū),即在進程中分配一個CRITICAL_SECTION數(shù)據(jù)結構,該臨界區(qū)結構的分配必須是全局的,這樣該進程的不同線程就能訪問它。關于CRITICAL_SECTION結構體的深入分析,可以參見文章<<Break Free of Code Deadlocks in Critical Sections Under Windows>> 2.在使用臨界區(qū)同步線程之前,必須調用InitializeCriticalSection來初始化臨界區(qū)。在釋放資源之前,只需要初始化一次。 3.VOID EnterCriticalSection:阻塞函數(shù)。調用線程不能獲取指定臨界區(qū)的所有權時,該線程將睡眠,且在被喚醒之前,系統(tǒng)不會給它分配CPU?;蛘呤褂肨ryEnterCriticalSection方法嘗試進入臨界區(qū),如果進入成功,則調用者線程獲得臨界區(qū)的使用權,否則返回失敗。 4.執(zhí)行臨界區(qū)內(nèi)的任務。 5.BOOL LeaveCriticalSection:非阻塞函數(shù)。將當前線程對指定臨界區(qū)的引用計數(shù)減1;在使用計數(shù)變?yōu)榱銜r,另一等待此臨界區(qū)的一個線程將被喚醒。 6.當不需要再使用該臨界區(qū)時,使用DeleteCriticalSection來釋放臨界區(qū)需要的資源。此函數(shù)執(zhí)行后,再也不能使用EnterCriticalSection和LeaveCriticalSection,除非再次使用InitializeCriticalSection初始化了該臨界區(qū)。 注意事項: 1.臨界區(qū)一次只允許一個線程訪問,每個線程必須在試圖操作臨界區(qū)域數(shù)據(jù)之前調用該臨界區(qū)域標志(即一個CRITICAL_SECTION全局變量)EnterCriticalSection后,其它想要獲得訪問權的線程都會置于睡眠狀態(tài),且在被喚醒以前,系統(tǒng)將停止為它們分配CPU時間片。換言之,臨界區(qū)可以且僅可被一個線程擁有,當然,沒有任何線程調用EnterCriticalSection或TryEnterCriticalSection時,臨界區(qū)不屬于任何 一個線程。 2.當擁有臨界區(qū)所有權的線程調用LeaveCriticalSection放棄所有權時,系統(tǒng)只喚醒等待隊列中的一個線程,給它所有權,其它線程則繼續(xù)等待。 3.注意,擁有該臨界區(qū)的線程,每一次針對此臨界區(qū)的EnterCriticalSection調用都會成功(這里指的是重復調用也會立即返回,也就是支持嵌套調用),且會使得臨界區(qū)標志(即一個CRITICAL_SECTION全局變量)的引用計數(shù)增加1。在另一個線程能夠擁有該臨界區(qū)之前,擁有它的線程必須調用LeaveCriticalSection足夠多次,在引用計數(shù)降為零后,另一線程才有可能擁有該臨界區(qū)。換言之,在一個正常使用臨界區(qū)的線程中,calSection和LeaveCriticalSection應該成對使用。 4.TryEnterCriticalSection
Mutex:(互斥對象包含一個使用數(shù)量,一個線程ID和一個引用計數(shù)器) 當兩個或更多線程需要同時訪問一個共享資源時,系統(tǒng)需要使用同步機制來確保一次只有一個線程使用該資源。Mutex只向一個線程授予對共享資源的獨占訪問權。 如果一個線程獲取了互斥體,則要獲取該互斥體的第二個線程將被掛起,直到第一個線程釋放該互斥體。 互斥量對象與所有其它內(nèi)核對象的不同之處在于它是被線程所擁有的。其它所有同步對象要么有信號,要么無信號,僅此而已。而互斥量對象除了記錄當前信號狀態(tài)外,還要記住此時那個線程擁有它。如果一個線程在得到一個互斥量對象 (即將其置為無信號態(tài))后就終結了,互斥量也就廢棄了。在這種情況下,互斥量將永遠保持無信號態(tài),因為沒有其它線程能夠通過調用ReleaseMutex來釋放它。系統(tǒng)發(fā)現(xiàn)產(chǎn)生這種情況時,就自動將互斥量設回有信號狀態(tài)。(將線程ID置為零,引用計數(shù)置零)其它等待該信號量的線程就會被喚醒,但函數(shù)的返回值為WAIT_ABANDONED而不是正常的WAIT_OBJECT_0。這時,其它線程可以通過等待的返回值知道互斥量是不是被正常釋放。 互斥量與CRITICAL_SECTION類似。擁有該互斥量的線程,每次調用WaitForSingleObject都會立即成功返回,但互斥量的使用計數(shù)將增加,同樣的,也要多次調用ReleaseMutex以使引用計數(shù)變?yōu)榱?,方可供別的線程使用。 問:其它內(nèi)核對象在線程異常終止沒有釋放所有權時,系統(tǒng)回重置其狀態(tài)嗎? 答:重置,但沒有任何標記,與正常釋放無異,即不會擁有互斥量的這個返回WAIT_ABANDONED的特性。 注意:線程擁有某個內(nèi)核對象和線程擁有某個內(nèi)核對象的所有權,這二者是不同的。當說線程擁有某個內(nèi)核對象時,強調的是當該線程終止時,若線程正好擁有該內(nèi)核對象的訪問權,內(nèi)核對象也將被廢棄,因為不能重置其信號狀態(tài);而線程擁有某一個內(nèi)核對象的所用權,指的是線程可以調用某些函數(shù),訪問該內(nèi)核對象或對該內(nèi)核對象執(zhí)行某些操作。 以互斥內(nèi)核對象來保持線程同步可能用到的函數(shù)主要有CreateMutex()、OpenMutex()、ReleaseMutex()、 WaitForSingleObject()和WaitForMultipleObjects()等。在使用互斥對象前,首先要通過 CreateMutex()或OpenMutex()創(chuàng)建或打開一個互斥對象。CreateMutex()函數(shù)原型為: HANDLE CreateMutex( 參數(shù)bInitialOwner主要用來控制互斥對象的初始狀態(tài)。一般多將其設置為FALSE,以表明互斥對象在創(chuàng)建時并沒有為任何線程所占有。如果在創(chuàng)建互斥對象時指定了對象名,那么可以在本進程其他地方或是在其他進程通過OpenMutex()函數(shù)得到此互斥對象的句柄。 OpenMutex()函數(shù)原型為:
Event:(分為自動重置事件和人工重置事件) 事件對象也可以通過通知操作的方式來保持線程的同步。主要函數(shù)有: CreateEvent(),OpenEvent(),SetEvent(),ResetEvent(),WaitForSingleObject()和WaitForMultipleObjects()等。
Semaphores:(允許多個線程同時訪問一個資源) 信號量對象對線程的同步方式與前面幾種方法不同,信號允許多個線程同時使用共享資源。它允許多個線程在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最大線程數(shù)目。在用CreateSemaphore()創(chuàng)建信號量時即要同時指出允許的最大資源計數(shù)和當前可用資源計數(shù)。一般是將當前可用資源計數(shù)設置為最大資源計數(shù),每增加一個線程對共享資源的訪問,當前可用資源計數(shù)就會減1,只要當前可用資源計數(shù)是大于0的,就可以發(fā)出信號量信號。但是當前可用計數(shù)減小到0時則說明當前占用資源的線程數(shù)已經(jīng)達到了所允許的最大數(shù)目,不能在允許其他線程的進入,此時的信號量信號將無法發(fā)出。線程在處理完共享資源后,應在離開的同時通過ReleaseSemaphore()函數(shù)將當前可用資源計數(shù)加1。在任何時候當前可用資源計數(shù)決不可能大于最大資源計數(shù)。 信號量是通過計數(shù)來對線程訪問資源進行控制的,而實際上信號量確實也被稱作Dijkstra計數(shù)器。 CreateSemaphore()用來創(chuàng)建一個信號量內(nèi)核對象,其函數(shù)原型為: OpenSemaphore()函數(shù)即可用來根據(jù)信號量名打開在其他進程中創(chuàng)建的信號量,函數(shù)原型如下: 信號量的使用特點使其更適用于對Socket(套接字)程序中線程的同步。例如,網(wǎng)絡上的HTTP服務器要對同一時間內(nèi)訪問同一頁面的用戶數(shù)加以限制,這時可以為沒一個用戶對服務器的頁面請求設置一個線程,而頁面則是待保護的共享資源,通過使用信號量對線程的同步作用可以確保在任一時刻無論有多少用戶對某一頁面進行訪問,只有不大于設定的最大用戶數(shù)目的線程能夠進行訪問,而其他的訪問企圖則被掛起,只有在有用戶退出對此頁面的訪問后才有可能進入。 總結: 互斥量與臨界區(qū)的作用非常相似,但互斥量是可以命名的,也就是說它可以跨越進程使用。所以創(chuàng)建互斥量需要的資源更多,所以如果只為了在進程內(nèi)部是用的話使用臨界區(qū)會帶來速度上的優(yōu)勢并能夠減少資源占用量。因為互斥量是跨進程的互斥量一旦被創(chuàng)建,就可以通過名字打開它。 互斥量(Mutex),信號燈(Semaphore),事件(Event)都可以被跨越進程使用來進行同步數(shù)據(jù)操作,而其他的對象與數(shù)據(jù)同步操作無關,但對于進程和線程來講,如果進程和線程在運行狀態(tài)則為無信號狀態(tài),在退出后為有信號狀態(tài)。所以可以使用WaitForSingleObject來等待進程和線程退出。 WaitForSingleObject,在一個指定時間(dwMilliseconds)內(nèi)等待某一個內(nèi)核對象變?yōu)橛行盘?,在此時間內(nèi),若等待的內(nèi)核對象一直是無信號的,則調用線程將睡眠,否則繼續(xù)執(zhí)行。超過此時間后,線程繼續(xù)運行。函數(shù)返回值可能為:WAIT_OBJECT_0、WAIT_TIMEOUT、WAIT_ABANDONED(僅當內(nèi)核對象為互斥量時)、WAIT_FAILED。 WaitForMultipleObjects與WaitForSingleObject類似,只是它要么等待指定列表(由lpHandles指定)中若干個對象(由nCount決定)都變?yōu)橛行盘?,要么等待一個列表(由lpHandles指定)中的某一個對象變?yōu)橛行盘枺ㄓ蒪WaitAll決定)。 WaitForSingleObject和WaitForMultipleObjects函數(shù)對特定的內(nèi)核對象有重要的副作用,即它們根據(jù)不同的內(nèi)核對象,會決定是否改變內(nèi)核對象的信號狀態(tài),并執(zhí)行這種改變;這些副作用,決定了是讓等待該內(nèi)核對象的進程或線程中的某一個被喚醒還是全都被喚醒。 (1) 對進程和線程內(nèi)核對象,這兩個函數(shù)不產(chǎn)生副作用。 在進程或線程內(nèi)核對象變?yōu)橛行盘柡?,它們將保持有信號,這兩個函數(shù)不會試圖改變內(nèi)核對象的信號狀態(tài)。這樣,所有等待這些內(nèi)核對象的線程都會被喚醒。 (2) 對于互斥量、自動重置事件和自動重置可等的計時器對象,這兩個函數(shù)將把它們的狀態(tài)改為無信號。 換言之一旦這些對象變?yōu)橛行盘柌⑶矣幸粋€線程被喚醒,則對象重被置為無信號狀態(tài)。于是,只有一個正在等待的線程醒來,其它等待的線程將繼續(xù)睡眠。 (3) 對于WaitForMultipleObjects函數(shù)還有非常重要的一個特性:當調用它時傳遞的bWaitAll為TRUE時,在所有被等待的對象都變?yōu)橛行盘栔埃坏却娜魏慰梢员桓淖儬顟B(tài)的內(nèi)核對象都不被重置為無信號狀態(tài)。換言之,在傳入?yún)?shù)bWaitAll為TRUE,WaitForMultipleObjects除非能取得所有指定對象(由lpHandles指定)的所有權,它不會取得單個對象的所有權(不能取得所有權,自然也不會改變此對象的信號狀態(tài))。這是為了防止死鎖。換言之,在bWaitAll為TRUE時,WaitForMultipleObjects不會在沒有獲得所有被等對象所有權的情形下改變某一可以被改變狀態(tài)的內(nèi)核對象的信號狀態(tài),任何以同樣方式等待的線程都不會被喚醒,但以其它方式等待的線程將被喚醒。 |
|
|