基元線程同步——內核模式構造(WaitHandle,EventWaitHandle,AutoResetEvent,ManualResetEvent,Semaphore,Mutex)


一、內核模式構造

內核模式構造,采用的是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  //超時的調用


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM