本章主要介紹下基於核心模式構造的執行緒同步方式,事件,訊號量。
閱讀目錄:
理論
Windows的執行緒同步方式可分為2種,使用者模式構造和核心模式構造。
核心模式構造:是由Windows系統本身使用,核心物件進行排程協助的。核心物件是系統地址空間中的一個記憶體塊,由系統建立維護。
核心物件為核心所擁有,而不為程式所擁有,所以不同程式可以訪問同一個核心物件, 如程式,執行緒,作業,事件,檔案,訊號量,互斥量等都是核心物件。
而訊號量,互斥體,事件是Windows專門用來幫助我們進行執行緒同步的核心物件。
對於執行緒同步操作來說,核心物件只有2個狀態, 觸發(終止,true)、未觸發(非終止,false)。 未觸發不可排程,觸發可排程。
使用者模式構造:是由特殊CPU指令來協調執行緒,上節講的volatile實現就是一種,Interlocked也是。 也可稱為非阻塞執行緒同步。
WaitHandle
在windows程式設計中,通過API建立一個核心物件後會返回一個控制程式碼,控制程式碼則是每個程式控制程式碼表的索引,而後可以拿到核心物件的指標、掩碼、標示等。
而WaitHandle抽象基類類作用是包裝了一個windows核心物件的控制程式碼。我們來看下其中一個WaitOne的函式原始碼(略精簡)。
public virtual bool WaitOne(TimeSpan timeout) { return WaitOne(timeout, false); } [System.Security.SecuritySafeCritical] // auto-generated [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread-safety.")] private bool WaitOne(long timeout, bool exitContext) { return InternalWaitOne(safeWaitHandle, timeout, hasThreadAffinity, exitContext); } [System.Security.SecurityCritical] internal static bool InternalWaitOne(SafeHandle waitableSafeHandle, long millisecondsTimeout, bool hasThreadAffinity, bool exitContext) { Contract.EndContractBlock(); int ret = WaitOneNative(waitableSafeHandle, (uint)millisecondsTimeout, hasThreadAffinity, exitContext); if (ret == WAIT_ABANDONED) { ThrowAbandonedMutexException(); } return (ret != WaitTimeout); } //呼叫win32 waitforsingleobjectEx [System.Security.SecurityCritical] [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] private static extern int WaitOneNative(SafeHandle waitableSafeHandle, uint millisecondsTimeout, bool hasThreadAffinity, bool exitContext);
WaitAll 和WaitAny 呼叫win32中,waitformultipleobjectsEx函式。
SignalAndWaitOne 呼叫win32中,signalandwait函式。
呼叫api帶ex都是設定超時的。 如果我們在c#中不傳,預設是-1 表示無限期等待。
其中SafeWaitHandle欄位,包含了一個win32核心物件控制程式碼。
理解了WaitHandle其他都好辦了,我們來看下它的派生型別。
WaitHandle
|——EventWaitHandle 事件構造。
|——AutoResetEvent
|——ManualResetEvent
|——Semaphore 訊號量構造。
|——Mutex 互斥體構造。
其中Semaphore和mutex第一篇已經介紹過了,下面來看看其他的。
AutoResetEvent
使用示例如下,有簡單註釋。 關於描述,儘量貼近系統自身術語。
static void Main(string[] args) { //AutoResetEvent example //AutoResetEvent 通知正在等待的執行緒已發生的事件。 AutoResetEvent waitHandler = new AutoResetEvent(false);//false 即非終止,未觸發。 new Thread(() => { waitHandler.WaitOne(); //阻塞當前執行緒,等待底層核心物件收到訊號。 Console.WriteLine("接收到訊號,開始處理。"); }).Start(); new Thread(() => { Thread.Sleep(2000); Console.WriteLine("發訊號"); waitHandler.Set(); //向核心物件傳送訊號。設定事件物件為非終止狀態、false,解除阻塞。 }).Start(); //waitHandler.Close(); //釋放控制程式碼資源。 //waitHandler.Reset(); //手動設定事件為非終止狀態、false,執行緒阻止。 Console.ReadLine(); }
WaitOne 阻塞執行緒,非自旋。
Set() 發出一個訊號後,設定事件狀態為false。 這本應該是2步的操作,AutoResetEvent.set()函式,給2步一起自動做了,很方便。
ManualResetEvent
這個和上面基本一樣,從字面來說需要手動重置狀態,我們來看例子。
ManualResetEvent manualWaitHandler = new ManualResetEvent(false);//false 即非終止,未觸發。 new Thread(() => { manualWaitHandler.WaitOne(); //阻塞當前執行緒物件,等待訊號。 Console.WriteLine("接收到訊號,開始處理。"); manualWaitHandler.Reset(); //手動 設定事件物件狀態為非終止狀態,false。 manualWaitHandler.WaitOne(); //這裡直接阻塞等待無效,因為事件物件還是true,必須手動調reset。 Console.WriteLine("第二次接收到訊號,開始處理。"); }).Start(); new Thread(() => { Thread.Sleep(2000); Console.WriteLine("發訊號"); manualWaitHandler.Set(); //向事件物件傳送ok訊號。。 Thread.Sleep(2000); Console.WriteLine("第二次發訊號"); manualWaitHandler.Set(); }).Start(); Console.ReadLine();
這2者區別很小,其實是系統Api的區分,不是net類庫實現的。
在Win32Native類中,我可以看到KERNEL32 api 有這麼個引數isManualReset。
[DllImport(KERNEL32, SetLastError=true, CharSet=CharSet.Auto, BestFitMapping=false)] [ResourceExposure(ResourceScope.Machine)] // Machine or none based on the value of "name" internal static extern SafeWaitHandle CreateEvent(SECURITY_ATTRIBUTES lpSecurityAttributes, bool isManualReset, bool initialState, String name);
總結
基於核心模式構造的同步步驟是: 託管程式碼->使用者模式程式碼->核心模式程式碼。
使用者模式構造, 是利用CPU特殊指令,進行原子操作。
使用者模式程式碼,如圖。 是指 託管程式碼呼叫 win32程式碼 這一層, 之後在調核心模式程式碼。
參考CLR via c#及Windows核心程式設計第五版。