1.1 線程同步概述
如果沒有同步對象和作業系統對特殊事件監視的能力,線程可能被迫使用有副作用的技術使自己與特殊事件同步。不使用作業系統支援的線程同步技術,會產生許多問題,比如:分配不必要的CPU時間,浪費;在高低優先順序線程間,若低線程負責訊號重設任務,則可能永遠無法執行重設。
1.2 臨界區1.2.1 概述
臨界區:在所有同步對象中,臨界區是最容易使用的,但它只能用於同步單個進程中的線程。臨界區一次只允許一個線程取得對某個資料區的訪問權。還有,在這些同步對象中,只有臨界區不是核心對象,它不由作業系統的低級組件管理,而且不能使用控制代碼來操縱。
1. 在進程中建立一個臨界區,即在進程中分配一個CRITICAL_SECTION資料結構,該臨界區結構的分配必須是全域的,這樣該進程的不同線程就能訪問它。
2. 在使用臨界區同步線程之前,必須調用InitializeCriticalSection來初始化臨界區。在釋放資源之前,只需要初始化一次。
3. VOID EnterCriticalSection:阻塞函數。The function returns when the calling thread is granted ownership。換言之,調用線程不能擷取指定臨界區的所有權時,該線程將睡眠,且在被喚醒之前,系統不會給它分配CPU。
4. 執行臨界區內的任務
5. BOOL LeaveCriticalSection:非阻塞函數。將當前線程對指定臨界區的引用計數減壹;在使用計數變為零時,另一等待此臨界區的一個線程將被喚醒。
6. 當不需要再使用該臨界區時,使用DeleteCriticalSection來釋放臨界區需要的資源。此函數執行後,再也不能使用EnterCriticalSection和LeaveCriticalSection,除非再次使用InitializeCriticalSection初始化了該臨界區。
1.2.2 注意事項:
1. 臨界區一次只允許一個線程訪問,每個線程必須在視圖操作臨界地區資料之前調用該臨界地區標誌(即一個CRITICAL_SECTION全域變數)EnterCriticalSection後,其它想要獲得訪問權的線程都會置於睡眠狀態,且在被喚醒以前,系統將停止為它們分配CPU時間片。換言之,臨界區可以且僅可被一個線程擁有,當然,沒有任何線程調用EnterCriticalSection或TryEnterCriticalSection時,臨界區不屬於任何一個線程。
2. 當擁有臨界區所有權的線程調用LeaveCriticalSection放棄所有權時,系統只喚醒正等待中的一個線程,給它所有權,其它線程則繼續睡眠。
3. 注意,擁有該臨界區的線程,每一次針對此臨界區的EnterCriticalSection調用都會成功(這裡指的是重複調用也會立即返回),且會使得臨界區標誌(即一個CRITICAL_SECTION全域變數)的引用計數增壹。在另一個線程能夠擁有該臨界區之前,擁有它的線程必須調用LeaveCriticalSection足夠多次,在引用計數降為零後,另一線程才有可能擁有該臨界區。換言之,在一個正常使用臨界區的線程中,calSection和LeaveCriticalSection應該成對使用。
4. TryEnterCriticalSection
BOOL TryEnterCriticalSection(
LPCRITICAL_SECTION lpCriticalSection
);
從函式宣告便可看出端倪,EnterCriticalSection函數的傳回值為VOID,而這裡為BOOL。可見對於TryEnterCriticalSection的調用,需要我們判斷其傳回值。在調用TryEnterCriticalSection時,如果指定的臨界區沒有被任何線程(或還沒有被任何調用線程)擁有,該函數將臨界區的訪問權給予調用的線程,並返回TRUE;不過,如果臨界區已經被另一個線程擁有,它立刻返回FALSE值。TryEnterCriticalSection和EnterCriticalSection之間的最大區別在於TryEnterCriticalSection從來不掛起線程。
1.3 用核心對象同步線程1.3.1 概述
臨界區非常適合於序列化對一個進程中的資料的訪問,因為它們的速度很快。但我們或許想要使一些應用程式與電腦中發生的其它特殊事件或者其它進程中執行的操作取得同步。這時臨界區無能為力。就需要使用核心對象來同步。
下列核心對象可用來同步線程:
1. 進程,Processes
2. 線程,Threads
3. 檔案,Files
4. 控制台輸入,Console input
5. 檔案變化通知,File change notifications
6. 互斥量,Mutexes
7. 訊號量,Semaphores
8. 事件(自動重設事件和手動重設事件),Events
9. 可等的計時器(只用於Window NT4或更高),Waitable timers
10. Jobs
每一個上面這些類型的對象都可以處於兩種狀態之一:有訊號(signaled)和無訊號(nonsignaled)。比如進程和線程在終結時其核心對象變為有訊號,而在它們處於建立和正在運行時,其核心對象是無訊號的。
1.3.2 核心對象同步應用
1. 某線程獲得某進程的核心物件控點時,它可以:改變進程優先順序、獲得進程的退出碼;使本線程與某進程的終結取得同步等等。
2. 當獲得某線程的核心物件控點時,它可以:改變該線程運行狀態、與該線程的終結取得同步等等。
3. 當獲得檔案控制代碼時,也可以:本線程可與某一個非同步檔案的I/O操作獲得同步等等。
4. 控制台輸入對象可用來使線程在有輸入進入時被喚醒以執行相關任務等等。
5. 其它核心對象―――檔案改變通知、互斥量、訊號量、事件、可等計時器等―――都只是為了同步對象而存在。相應的,也有WIN32函數來建立、開啟、關閉這些對象,將線程與這些對象同步。對這些對象,沒有其它操作可以執行了。
1.3.3 互斥量專屬的特性(另參附錄的實驗)
互斥量對象與所有其它核心對象的不同之處在於它是被線程所擁有的。其 它所有同步對象要麼有訊號,要麼無訊號,僅此而已。而互斥量對象除了記錄當前訊號狀態外,還要記住此時那個線程擁有它。如果一個線程在得到一個互斥量對象 (即將其置為無訊號態)後就終結了,互斥量也就廢棄了。在這種情況了,互斥量將永遠保持無訊號態,因為沒有其它線程能夠通過調用ReleaseMutex來釋放它。
系統發現產生這種情況時,就自動將互斥量設回有訊號狀態。其它等待該訊號量的線程就會被喚醒,但函數的傳回值為WAIT_ABANDONED而不是正常的WAIT_OBJECT_0。這時,其它線程可以知道互斥量是不是被正常釋放。
其它的,互斥量與CRITICAL_SECTION類似。擁有該互斥量的線程,每次調用WaitForSingleObject都會立即成功返回,但互斥量的使用計數將增加,同樣的,也要多次調用ReleaseMutex以使引用計數變為零,方可供別的線程使用。