一、內核模式構造
內核模式構造,采用的是windows操作系統來同步線程,比VolatileRead,VolatileWrite,Interlocked等用戶模式的構造慢很多。相對於用戶模式的構造,它也有自己的優點:
1,不用像用戶模式那樣占着cpu“自旋”,浪費cpu資源。
2,內核模式可同步在同一機器不同進程中運行的線程。
3,可實現本地和托管線程相互之間的同步。
4,一個線程可以一直阻塞,直到一個集合中的內核對象全部可用,或部分可用。(WaitAll,WaitAny)
5,阻塞一個線程時,可以指定一個超時值,超過這個時間就解除阻塞。
二、FCL提供的內核模式構造層次結構
WaitHandle(抽象類)
|——EventWaitHandle
|——AutoResetEvent
|——ManualResetEvent
|——Semaphore
|——Mutex
他們都繼承了WaitHandle抽象類,WaitHandle提供了下列共同的靜態方法:
WaitOne:阻塞調用線程,直到收到一個信號。
WaitAny:阻塞調用線程,直到收到任意一個信號。
WaitAll:阻塞調用線程,直到收到全部信號。
SingleAndWait:向指定的內核對象發出信號,並等待另一個內核對象收到信號。
Close/Dispose:關閉內核對象句柄。
2.1 EventWaitHandle
它屬於事件(event),事件是內核維護的Boolean變量。如果事件為false,在事件上等待的線程就阻塞;如果事件為true,就解除阻塞。它主要有兩個方法:
Set:將事件設為true。
ReSet:將事件設為false。
注意:初始化的時候我們可以指定事件的初始值,比如下面的例子就是指定初始時事件為false。
EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.AutoReset);//等同於AutoResetEvent EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.ManualReset);//等同於ManualResetEvent
2.2 AutoResetEvent
AutoResetEvent是EventWaitHandle的一個簡單包裝,內部沒有額外的任何邏輯。它最大的特點就是,調用了Set方法將事件設為true之后,其中一個等待線程得到執行后,它會自動調用Reset方法,將事件信號設為false,以阻塞其它的線程。相當於放一個線程進來,門自動就關了(自動門)。
例子,使用AutoResetEvent實現一個簡單的線程同步鎖。
private class SimpleWaitLock : IDisposable { //初始化一定要是true,否者,第一個調用Enter方法的線程會被阻塞 private AutoResetEvent are = new AutoResetEvent(true); #region IDisposable public void Enter() { are.WaitOne();//第一個線程調用這個方法后,事件將會為false,其他線程會被阻塞 Console.WriteLine("thread={0}", Thread.CurrentThread.ManagedThreadId); } public void Exit() { are.Set();//退出時,將事件信號設為true,放一個線程進來后,馬上設為false(調用reset) } public void Dispose() { are.Dispose(); } #endregion }
2.3 ManualResetEvent
ManualResetEvent是EventWaitHandle的一個簡單包裝,內部也沒有額外的任何邏輯。它和AutoResetEvent唯一的不同是,調用了Set方法將事件設為true后,不會去調用Reset方法,這將導致事件一直處於true,其它等待的多個線程都會得到執行,直到你手動調用Reset方法。相當於你把門打開后,需要手動去關(非自動門)。
2.4 Semaphore
信號量(semaphore)是內核維護的一個Int32的變量。信號量為0時,在信號量上等待的線程會阻塞;信號量大於0時,就解除阻塞。主要方法:
Release():就是一個加1的操作
Release(int32 releasecount):就是一個加releasecount的操作。
初始化semaphore時,可以指定最大和最小信號量的值。
用Sempphore實現同樣功能的同步鎖:
private class SimpleWaitLock : IDisposable { //初始化指定計數值為1,允許第一個線程可用 private Semaphore sp = new Semaphore(1, 1); #region IDisposable public void Enter() { sp.WaitOne();//第一個線程調用這個方法后,計數值減1,變為0,其他線程會被阻塞 Console.WriteLine("thread={0}", Thread.CurrentThread.ManagedThreadId); } public void Exit() { sp.Release();//計數值加1,其他線程可用 } public void Dispose() { sp.Dispose(); } #endregion }
2.5 Mutex
互斥體(mutex)和計數值為1的Semaphore或AutoResetEvent的工作方式非常相似。這三種方式每次都只釋放一個等待的線程。主要方法:
ReleaseMutex():計數減去1
它有一個最大的不同是,它可以在同一線程上循環調用,也就是多次調用WaitOne(),然后在調用等次數的ReleaseMutex()。直到Mutex的計數為0時,其他等待的線程才能被調用。這種方式在平常中可能不太會用到。
可以用Mutex來防止應用程序二次啟動,這在平常工作中也經常會碰到。
簡單示例:
static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); bool createNew; Mutex mutex = new Mutex(false, "ApplicationGuidName", out createNew); //沒有啟動,就創建一個新的 if (createNew) { Application.Run(new Form1()); } else { // 已經啟動了 MessageBox.Show("程序已經啟動,不能重復啟動!"); } }
注意:也可以用Semaphore和EventWaithandle來實現上面的功能,原理是一樣的。但AutoResetEvent和ManualResetEvnet不行,他們沒有提供類似的構造方法。
2.6 在一個內核模式變得可用時調用一個方法
可以通過ThreadPool.RegisterWaitForSingleObject方法注冊一個方法。當一個事件收到信號,或是指定的時間超時,就會自動調用這個方法。
這個方法對於AutoResetEvent特別有用。但不太適合ManualResetEvent,因為要手動去調用Reset方法,不然會無限的調用這個方法。
private void TestAutoCallBack() { AutoResetEvent mre = new AutoResetEvent(false); //設定超時為2000ms ThreadPool.RegisterWaitForSingleObject(mre, new WaitOrTimerCallback(WaitCallBack), "123", 2000, false); //發出一個信號 mre.Set(); //故意等代3000ms Thread.Sleep(3000); } //有信號或超時就會調用這個方法 private void WaitCallBack(object state, bool timeOut) { Console.WriteLine("is time out = {0}", timeOut); }
運行結果:
is time out = False //得到信號時的調用 is time out = True //超時的調用 is time out = True //超時的調用