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