1.1 線(xiàn)程同步概述
如果沒(méi)有同步對象和操作系統對特殊事件監視的能力,線(xiàn)程可能被迫使用有副作用的技術(shù)使自己與特殊事件同步。不使用操作系統支持的線(xiàn)程同步技術(shù),會(huì )產(chǎn)生許多問(wèn)題,比如:分配不必要的CPU時(shí)間,浪費;在高低優(yōu)先級線(xiàn)程間,若低線(xiàn)程負責信號重置任務(wù),則可能永遠無(wú)法執行重置。
1.2 臨界區
1.2.1 概述
臨界區:在所有同步對象中,臨界區是最容易使用的,但它只能用于同步單個(gè)進(jìn)程中的線(xiàn)程。臨界區一次只允許一個(gè)線(xiàn)程取得對某個(gè)數據區的訪(fǎng)問(wèn)權。還有,在這些同步對象中,只有臨界區不是內核對象,它不由操作系統的低級部件管理,而且不能使用句柄來(lái)操縱。
1. 在進(jìn)程中創(chuàng )建一個(gè)臨界區,即在進(jìn)程中分配一個(gè)CRITICAL_SECTION數據結構,該臨界區結構的分配必須是全局的,這樣該進(jìn)程的不同線(xiàn)程就能訪(fǎng)問(wèn)它。
2. 在使用臨界區同步線(xiàn)程之前,必須調用InitializeCriticalSection來(lái)初始化臨界區。在釋放資源之前,只需要初始化一次。
3. VOID EnterCriticalSection:阻塞函數。The function returns when the calling thread is granted ownership。換言之,調用線(xiàn)程不能獲取指定臨界區的所有權時(shí),該線(xiàn)程將睡眠,且在被喚醒之前,系統不會(huì )給它分配CPU。
4. 執行臨界區內的任務(wù)
5. BOOL LeaveCriticalSection:非阻塞函數。將當前線(xiàn)程對指定臨界區的引用計數減壹;在使用計數變?yōu)榱銜r(shí),另一等待此臨界區的一個(gè)線(xiàn)程將被喚醒。
6. 當不需要再使用該臨界區時(shí),使用DeleteCriticalSection來(lái)釋放臨界區需要的資源。此函數執行后,再也不能使用EnterCriticalSection和LeaveCriticalSection,除非再次使用InitializeCriticalSection初始化了該臨界區。
1.2.2 注意事項:
1. 臨界區一次只允許一個(gè)線(xiàn)程訪(fǎng)問(wèn),每個(gè)線(xiàn)程必須在視圖操作臨界區域數據之前調用該臨界區域標志(即一個(gè)CRITICAL_SECTION全局變量)EnterCriticalSection后,其它想要獲得訪(fǎng)問(wèn)權的線(xiàn)程都會(huì )置于睡眠狀態(tài),且在被喚醒以前,系統將停止為它們分配CPU時(shí)間片。換言之,臨界區可以且僅可被一個(gè)線(xiàn)程擁有,當然,沒(méi)有任何線(xiàn)程調用EnterCriticalSection或TryEnterCriticalSection時(shí),臨界區不屬于任何一個(gè)線(xiàn)程。
2. 當擁有臨界區所有權的線(xiàn)程調用LeaveCriticalSection放棄所有權時(shí),系統只喚醒正等待中的一個(gè)線(xiàn)程,給它所有權,其它線(xiàn)程則繼續睡眠。
3. 注意,擁有該臨界區的線(xiàn)程,每一次針對此臨界區的EnterCriticalSection調用都會(huì )成功(這里指的是重復調用也會(huì )立即返回),且會(huì )使得臨界區標志(即一個(gè)CRITICAL_SECTION全局變量)的引用計數增壹。在另一個(gè)線(xiàn)程能夠擁有該臨界區之前,擁有它的線(xiàn)程必須調用LeaveCriticalSection足夠多次,在引用計數降為零后,另一線(xiàn)程才有可能擁有該臨界區。換言之,在一個(gè)正常使用臨界區的線(xiàn)程中,calSection和LeaveCriticalSection應該成對使用。
1.3 用內核對象同步線(xiàn)程
1.3.1 概述
臨界區非常適合于序列化對一個(gè)進(jìn)程中的數據的訪(fǎng)問(wèn),因為它們的速度很快。但我們或許想要使一些應用程序與計算機中發(fā)生的其它特殊事件或者其它進(jìn)程中執行的操作取得同步。這時(shí)臨界區無(wú)能為力。就需要使用內核對象來(lái)同步。
下列內核對象可用來(lái)同步線(xiàn)程:
1. 進(jìn)程,Processes
2. 線(xiàn)程,Threads
3. 文件,Files
4. 控制臺輸入,Console input
5. 文件變化通知,File change notifications
6. 互斥量,Mutexes
7. 信號量,Semaphores
8. 事件(自動(dòng)重設事件和手動(dòng)重設事件),Events
9. 可等的計時(shí)器(只用于Window NT4或更高),Waitable timers
10. Jobs
每一個(gè)上面這些類(lèi)型的對象都可以處于兩種狀態(tài)之一:有信號(signaled)和無(wú)信號(nonsignaled)。比如進(jìn)程和線(xiàn)程在終結時(shí)其內核對象變?yōu)橛行盘?,而在它們處于?chuàng )建和正在運行時(shí),其內核對象是無(wú)信號的。
1.3.2 內核對象同步應用
1. 某線(xiàn)程獲得某進(jìn)程的內核對象句柄時(shí),它可以:改變進(jìn)程優(yōu)先級、獲得進(jìn)程的退出碼;使本線(xiàn)程與某進(jìn)程的終結取得同步等等。
2. 當獲得某線(xiàn)程的內核對象句柄時(shí),它可以:改變該線(xiàn)程運行狀態(tài)、與該線(xiàn)程的終結取得同步等等。
3. 當獲得文件句柄時(shí),也可以:本線(xiàn)程可與某一個(gè)異步文件的I/O操作獲得同步等等。
4. 控制臺輸入對象可用來(lái)使線(xiàn)程在有輸入進(jìn)入時(shí)被喚醒以執行相關(guān)任務(wù)等等。
5. 其它內核對象―――文件改變通知、互斥量、信號量、事件、可等計時(shí)器等―――都只是為了同步對象而存在。相應的,也有WIN32函數來(lái)創(chuàng )建、打開(kāi)、關(guān)閉這些對象,將線(xiàn)程與這些對象同步。對這些對象,沒(méi)有其它操作可以執行了。
1.3.3 互斥量獨有的特性(另參附錄的實(shí)驗)
互斥量對象與所有其它內核對象的不同之處在于它是被線(xiàn)程所擁有的。其它所有同步對象要么有信號,要么無(wú)信號,僅此而已。而互斥量對象除了記錄當前信號狀態(tài)外,還要記住此時(shí)那個(gè)線(xiàn)程擁有它。如果一個(gè)線(xiàn)程在得到一個(gè)互斥量對象(即將其置為無(wú)信號態(tài))后就終結了,互斥量也就廢棄了。在這種情況了,互斥量將永遠保持無(wú)信號態(tài),因為沒(méi)有其它線(xiàn)程能夠通過(guò)調用ReleaseMutex來(lái)釋放它。
系統發(fā)現產(chǎn)生這種情況時(shí),就自動(dòng)將互斥量設回有信號狀態(tài)。其它等待該信號量的線(xiàn)程就會(huì )被喚醒,但函數的返回值為WAIT_ABANDONED而不是正常的WAIT_OBJECT_0。這時(shí),其它線(xiàn)程可以知道互斥量是不是被正常釋放。
其它的,互斥量與CRITICAL_SECTION類(lèi)似。擁有該互斥量的線(xiàn)程,每次調用WaitForSingleObject都會(huì )立即成功返回,但互斥量的使用計數將增加,同樣的,也要多次調用ReleaseMutex以使引用計數變?yōu)榱?,方可供別的線(xiàn)程使用。
1.3.3.1 疑問(wèn)
問(wèn):其它內核對象在線(xiàn)程異常終止沒(méi)有釋放所有權時(shí),系統回重置其狀態(tài)嗎?如果重置,將沒(méi)有任何標記,與正常釋放無(wú)異,即不會(huì )擁有互斥量的這個(gè)返回WAIT_ABANDONED的特性?
【注意:線(xiàn)程擁有某個(gè)內核對象和線(xiàn)程擁有某個(gè)內核對象的所有權,這二者是不同的。當說(shuō)線(xiàn)程擁有某個(gè)內核對象時(shí),要強調的是當該線(xiàn)程終止時(shí),若線(xiàn)程正好擁有該內核對象的訪(fǎng)問(wèn)權,內核對象也將被廢棄―――因為不能重置其信號狀態(tài);而線(xiàn)程擁有某一個(gè)內核對象的所用權,指的是線(xiàn)程可以調用某些函數,訪(fǎng)問(wèn)該內核對象或對該內核對象執行某些操作】
答:見(jiàn)附錄中后面的“討論與實(shí)驗一”。
1.3.4 WaitForSingleObject與WaitForMultipleObjects
線(xiàn)程主要使用兩個(gè)函數將它們設為睡眠來(lái)等待內核對象變?yōu)?strong>有信號:即都是阻塞函數。
DWORD WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds
);
DWORD WaitForMultipleObjects(
DWORD nCount,
const HANDLE* lpHandles,
BOOL bWaitAll,
DWORD dwMilliseconds
);
WaitForSingleObject,在一個(gè)指定時(shí)間(dwMilliseconds)內等待某一個(gè)內核對象變?yōu)橛行盘?,在此時(shí)間內,若等待的內核對象一直是無(wú)信號的,則調用線(xiàn)程將睡眠,否則繼續執行。超過(guò)此時(shí)間后,線(xiàn)程繼續運行。函數返回值可能為:WAIT_OBJECT_0、WAIT_TIMEOUT、WAIT_ABANDONED(僅當內核對象為互斥量時(shí))、WAIT_FAILED。
WaitForMultipleObjects與WaitForSingleObject類(lèi)似,只是它要么等待指定列表(由lpHandles指定)中若干個(gè)對象(由nCount決定)都變?yōu)橛行盘?,要么等待一個(gè)列表(由lpHandles指定)中的某一個(gè)對象變?yōu)橛行盘枺ㄓ蒪WaitAll決定)。
WaitForSingleObject和WaitForMultipleObjects函數對特定的內核對象有重要的副作用―――即它們根據不同的內核對象,會(huì )決定是否改變內核對象的信號狀態(tài),并執行這種改變;這些副作用,決定了是讓等待該內核對象的進(jìn)程或線(xiàn)程中的某一個(gè)被喚醒還是全都被喚醒。
1、 對進(jìn)程和線(xiàn)程內核對象,這兩個(gè)函數不產(chǎn)生副作用。即,在進(jìn)程或線(xiàn)程內核對象變?yōu)橛行盘柡?,它們將保持有信號,這兩個(gè)函數不會(huì )試圖改變內核對象的信號狀態(tài)。這樣,所有等待這些內核對象的線(xiàn)程都會(huì )被喚醒。
The WaitForSingleObject function checks the current state of the specified object. If the object's state is nonsignaled, the calling thread enters the wait state. It uses no processor time while waiting for the object state to become signaled or the time-out interval to elapse.
The function modifies the state of some types of synchronization objects. Modification occurs only for the object whose signaled state caused the function to return. For example, the count of a semaphore object is decreased by one.
2、 對于互斥量、自動(dòng)重置事件和自動(dòng)重置可等的計時(shí)器對象,這兩個(gè)函數將把它們的狀態(tài)改為無(wú)信號。換言之,一旦這些對象變?yōu)橛行盘柌⑶矣幸粋€(gè)線(xiàn)程被喚醒,則對象重被置為無(wú)信號狀態(tài)。于是,只有一個(gè)正在等待的線(xiàn)程醒來(lái),其它等待的線(xiàn)程將繼續睡眠。
3、 對于WaitForMultipleObjects函數還有非常重要的一個(gè)特性:當調用它時(shí)傳遞的bWaitAll為T(mén)RUE時(shí),在所有被等待的對象都變?yōu)橛行盘栔?,被等待的任何可以被改變狀態(tài)的內核對象都不被重置為無(wú)信號狀態(tài)。換言之,在傳入參數bWaitAll為T(mén)RUE,WaitForMultipleObjects除非能取得所有指定對象(由lpHandles指定)的所有權,它不會(huì )取得單個(gè)對象的所有權(不能取得所有權,自然也不會(huì )改變此對象的信號狀態(tài))。這是為了防止死鎖。換言之,在bWaitAll為T(mén)RUE時(shí),WaitForMultipleObjects不會(huì )在沒(méi)有獲得所有被等對象所有權的情形下改變某一可以被改變狀態(tài)的內核對象的信號狀態(tài),任何以同樣方式等待的線(xiàn)程都不會(huì )被喚醒―――但以其它方式等待的線(xiàn)程將被喚醒。
The WaitForMultipleObjects function determines whether the wait criteria have been met. If the criteria have not been met, the calling thread enters the wait state. It uses no processor time while waiting for the criteria to be met.
When bWaitAll is TRUE, the function's wait operation is completed only when the states of all objects have been set to signaled. The function does not modify the states of the specified objects until the states of all objects have been set to signaled. For example, a mutex can be signaled, but the thread does not get ownership until the states of the other objects are also set to signaled. In the meantime, some other thread may get ownership of the mutex, thereby setting its state to nonsignaled.
The function modifies the state of some types of synchronization objects. Modification occurs only for the object or objects whose signaled state caused the function to return. For example, the count of a semaphore object is decreased by one. When bWaitAll is FALSE, and multiple objects are in the signaled state, the function chooses one of the objects to satisfy the wait(到底選擇哪一個(gè)呢?天知道!); the states of the objects not selected are unaffected.
1.3.5 事件或定時(shí)器的自動(dòng)重置與手動(dòng)重置
1. 自動(dòng)重置:在內核對象變?yōu)橛行盘枙r(shí),僅有一個(gè)線(xiàn)程可以獲得它,該線(xiàn)程一旦獲得,內核對象又將變?yōu)闊o(wú)信號態(tài),其它等待的線(xiàn)程將繼續睡眠。由Wait類(lèi)函數實(shí)現自動(dòng)重置未無(wú)信號狀態(tài)的功能。
2. 手動(dòng)重置:一般來(lái)說(shuō),所有等待該信號的線(xiàn)程都會(huì )蘇醒過(guò)來(lái)。在你調用相關(guān)函數將內核對象置為無(wú)信號狀態(tài)前,內核對象將一直處于信號狀態(tài)。
2 附錄
2.1 討論與實(shí)驗一:內核對象處于非信號狀態(tài)時(shí),相關(guān)線(xiàn)程結束時(shí)內核對象是否被系統重置為信號臺?
互斥量被某一線(xiàn)程擁有,若線(xiàn)程退出但未釋放相應互斥量,操作系統將調用ReleaseMutex釋放相應互斥量。對于信號量和事件呢?若沒(méi)有被正確釋放或重置,操作系統是否在進(jìn)程結束后嘗試像對互斥量進(jìn)行釋放的操作?――若不,那么,對沒(méi)有正確釋放的信號量和事件,在操作系統的本次運行期間將一直存在,是么?:舉例來(lái)說(shuō),若進(jìn)程使用某一事件來(lái)完成唯一實(shí)例,若進(jìn)程退出后沒(méi)有正確釋放相應事件,那么,在系統重新啟動(dòng)之前,進(jìn)程將不能運行。是么?
注意,對于內核對象來(lái)說(shuō),若其引用計數變?yōu)?,系統將銷(xiāo)毀該內核對象。為了比較,我以Event和Mutex分別構造幾個(gè)應用程序。(注意,Mutex是屬于線(xiàn)程的,但這里的應用程序都是單線(xiàn)程的,換言之,這里沒(méi)有測試到進(jìn)程內某一擁有互斥量的線(xiàn)程不釋放互斥量便退出的情況?。?/div>
2.1.1 Event程序
使用Event的應用程序有四。Event創(chuàng )建時(shí)置為手動(dòng)重置事件,初始狀態(tài)為有信號狀態(tài),換言之,以::CreateEvent(NULL, TRUE, TRUE, "KernelObjectsEvent ")方式創(chuàng )建。還有,以WaitForSingleObject(內核對象句柄,0)方式等待“KernelObjectsEvent”名的Event對象。
1. Event_OnlyReferences~僅僅嘗試用CreateEvent獲取Event句柄,不等待該Event,也不改變它的任何狀態(tài)。
2. Event_AfterWaitnonSignaled~同樣方式調用CreateEvent,然后等待,成功后置為無(wú)信號態(tài)。應用程序繼續運行。
3. Event_setEventBeforeWaitAfternonSignaled~同樣方式調用CreateEvent,但在等待前置事件為信號臺,然后等待,成功后置為無(wú)信號態(tài)。
4. Event_setEventBeforeWaitAfterSignaled~同樣方式調用CreateEvent,但在等待前置事件為信號臺,然后等待,成功后置為有信號態(tài)。
2.1.2 Mutex程序
使用Mutex的應用程序有四。Mutex創(chuàng )建時(shí)置為非進(jìn)程擁有,換言之,以CreateMutex(NULL, FALSE, "KernelObjectsMutex")方式創(chuàng )建。還有,以WaitForSingleObject(內核對象句柄,0)方式等待“KernelObjectsMutex”名的Mutex對象
1. Mutex_OnlyReferences~僅僅嘗試用CreateMutex獲取Mutex句柄,不等待該Mutex,也不會(huì )嘗試釋放它。
2. Mutex_AfternonSignaled~同樣方式調用CreateMutex,然后等待,成功后并不嘗試釋放它。應用程序繼續運行。
3. Mutex_ReleaseBeforeWaitAfternonSignaled~同樣方式調用CreateMutex,但在等待前嘗試多次調用ReleaseMutex釋放它,然后等待,成功后并不嘗試釋放它。
4. Mutex_ReleaseBeforeWaitAfterSignaled~同樣方式調用CreateMutex,但在等待前嘗試多次調用ReleaseMutex釋放它,然后等待,成功后置再次釋放它。
2.1.3 實(shí)驗一
2.1.3.1 Event
1. Event_OnlyReferences……運行成功,別終止程序。
2. 第一次運行Event_AfterWaitnonSignaled……運行成功,別終止程序。
3. 第二次運行Event_AfterWaitnonSignaled……運行不成功,程序退出。
4. Event_setEventBeforeWaitAfternonSignaled……運行成功,別終止程序。
5. 第三次運行Event_AfterWaitnonSignaled……運行不成功,程序退出。
6. Event_setEventBeforeWaitAfterSignaled……運行成功,別終止程序。
7. 第四次運行Event_AfterWaitnonSignaled……運行成功,別終止程序。
2.1.3.2 Mutex
1. Mutex_OnlyReferences……運行成功,別終止程序。
2. 第一次運行Mutex_AfternonSignaled……運行成功,別終止程序。
3. 第二次運行Mutex_AfternonSignaled……運行不成功,程序退出。
4. Mutex_ReleaseBeforeWaitAfternonSignaled……運行不成功,程序退出。
5. 第三次運行Mutex_AfternonSignaled……運行不成功,程序退出。
6. Mutex_ReleaseBeforeWaitAfterSignaled……運行不成功,程序退出。
7. 第三次運行Mutex_AfternonSignaled……運行不成功,程序退出。
2.1.3.3 結論
1. 只有當前擁有Mutex的線(xiàn)程調用ReleaseMutex函數才會(huì )返回TRUE,并且才會(huì )釋放該Mutex;不擁有該Mutex的線(xiàn)程調用ReleaseMutex函數就是簡(jiǎn)單的返回FALSE。
2. 而Mutex并不屬于某一個(gè)線(xiàn)程擁有,它屬于操作系統,不關(guān)當前事件是有信號還是無(wú)信號,你都能調用SetEvent或ResetEvent改變其狀態(tài)。―――換言之,如果你在你的應用程序中任意一處沒(méi)有正確的按照規則調用SetEvent或ResetEvent,你將達不到同步或互斥的目的。
3.
【注意:一般來(lái)說(shuō),都是利
用Event來(lái)進(jìn)行同步,而不是我們這里的讓它來(lái)達到互斥;而作為
互斥的
與線(xiàn)程無(wú)關(guān)的內核對象應該是Semaphore;而作為
互斥的
與線(xiàn)程有關(guān)的內核對象應該是Mutex?!?/div>
2.1.4 實(shí)驗二
2.1.4.1 Event
1. 運行Event_OnlyReferences……運行成功,別終止程序。
2. 第一次運行Event_AfterWaitnonSignaled……運行成功,別終止程序。
3. 運行Event_setEventBeforeWaitAfternonSignaled……運行成功,別終止程序。
4. 除了第一次運行的Mutex_OnlyReferences外,關(guān)閉所有其它應用程序。
5. 第二次運行Event_AfterWaitnonSignaled……運行不成功,程序退出。
6. 運行Event_setEventBeforeWaitAfterSignaled……運行成功,別終止程序。
7. 除了第一運行的Event_OnlyReferences外,關(guān)閉所有其它程序。
8. 第三次運行Event_AfterWaitnonSignaled程序……運行成功,別終止程序。
2.1.4.2 Mutex
1. 運行Mutex_OnlyReferences……運行成功,別終止程序。
2. 第一次運行Mutex_AfternonSignaled……運行成功,別終止程序。
3. 運行Mutex_ReleaseBeforeWaitAfternonSignaled……運行不成功,程序退出。
4. 除了第一次運行的Mutex_OnlyReferences外,關(guān)閉所有其它應用程序。
5. 第二次運行Mutex_AfternonSignaled……捕捉到WAIT_ABANDONED信號,這里我們讓程序繼續運行。別中止程序。
6. 運行Mutex_ReleaseBeforeWaitAfterSignaled……運行不成功,程序退出。
7. 除了第一次運行的Mutex_OnlyReferences外,關(guān)閉所有其它應用程序。
8. 第三次運行Mutex_AfternonSignaled……運行成功,別終止程序。
2.1.4.3 結論
1. Event處于無(wú)信號狀態(tài)時(shí),相關(guān)線(xiàn)程或進(jìn)程退出,系統并不會(huì )嘗試為你將其置為有信號狀態(tài)―――所以Event實(shí)驗中第5步運行不成功。但由于它(這里的它是內核對象,而不是內核對象的句柄)不屬于任何進(jìn)程或線(xiàn)程,你可以再任何別的程序中重新將它置為信號臺,所以第6步執行成功。
2. Mutex處于無(wú)信號狀態(tài)時(shí),當前擁有它的線(xiàn)程或進(jìn)程退出,系統會(huì )嘗試將其置為有信號狀態(tài)―――所以Mutex實(shí)驗中第5步運行成功,但捕捉到WAIT_ABANDONED信號,它指明是操作系統為你重置為有信號狀態(tài)的。同樣,由于某一線(xiàn)程現在擁有它,它未釋放該互斥量且線(xiàn)程未退出時(shí),它屬于這個(gè)線(xiàn)程,你不能剝奪它的權利,所以第6步運行不成功。
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請
點(diǎn)擊舉報。