AutoResetEvent控制線程用法

<div style="background-color:#226DDD;width:100%;padding:10px;auto;text-indent:2em"><font color=#FFFFFF face="Microsoft YaHei" style="font-size:13px"> 本文主要來自一道面試題,因爲以前對AutoResetEvent的概念比較模糊(即便已經使用過了)。面試題題目很簡潔:兩個線程交替打印0~100的奇偶數。你能夠先動手試試,我主要是嘗試在一個方法裏面完成這個任務。 <br/>注: Suspend,Resume來控制線程已經在.net framework2.0被淘汰了,緣由就是掛起以後,但由於異常而沒有及時恢復,若是佔用資源會致使死鎖。 </font></div>html

AutoResetEvent概念

  • AutoResetEvent對象用來進行線程同步操做,AutoResetEvent類繼承waitHandle類。waitOne()方法就繼承來自waitHandle類。
  • AutoResetEvent對象有終止和非終止兩種狀態,終止狀態是線程繼續執行,非終止狀態使線程阻塞,能夠調用set和reset方法使對象進入終止和非終止狀態。-》<font color=red>能夠簡單理解若是AutoResetEvent對象是終止狀態,就像無論別人了,任你撒野去(waitOne()獲得的都是撒野信號)</font>
  • AutoResetEvent顧名思義,其對象在調用一次set以後會自動調用一次reset,進入非終止狀態使調用了等待方法的線程進入阻塞狀態。-》<font color=red>能夠簡單理解若是AutoResetEvent對象是非終止狀態,就開始管理起別人來了,此時waitOne()獲得的信號都是呆在原地不動信號。</font>
  • waitHandle對象的waitone能夠使當前線程進入阻塞狀態,等待一個信號。直到當前 waitHandle對象收到信號,纔會繼續執行。
  • set能夠發送一個信號,容許一個調用waitone而等待線程繼續執行。 ManulResetEvent的set方法能夠容許多個。可是要手動關閉,即調用reset();
  • reset能夠使由於調用waitone() 而等待線程都進入阻塞狀態。

AutoResetEvent主要方法及實踐

  1. AutoResetEvent(bool initialState):構造函數,用一個指示是否將初始狀態設置爲終止的布爾值初始化該類的新實例。 false:無信號,子線程的WaitOne方法不會被自動調用 true:有信號,子線程的WaitOne方法會被自動調用
  2. Reset ():將事件狀態設置爲非終止狀態,致使線程阻止;若是該操做成功,則返回true;不然,返回false。
  3. Set ():將事件狀態設置爲終止狀態,容許一個或多個等待線程繼續;若是該操做成功,則返回true;不然,返回false。
  4. WaitOne(): 阻止當前線程,直到收到信號。
  5. WaitOne(TimeSpan, Boolean) :阻止當前線程,直到當前實例收到信號,使用 TimeSpan 度量時間間隔並指定是否在等待以前退出同步域。

有了上面的解釋,開始展現代碼(通過屢次優化)面試

//若要將初始狀態設置爲終止,則爲 true;若要將初始狀態設置爲非終止,則爲 false
        static AutoResetEvent oddResetEvent = new AutoResetEvent(false);
        static AutoResetEvent evenResetEvent = new AutoResetEvent(false);
        static int i = 0;
        static void Main(string[] args)
        {
            //ThreadStart是個委託
            Thread thread1 = new Thread(new ThreadStart(show));
            thread1.Name = "偶數線程";
            Thread thread2 = new Thread(new ThreadStart(show));
            thread2.Name = "奇數線程";
            thread1.Start();
           Thread.Sleep(2); //保證偶數線程先運行。
            thread2.Start();
            Console.Read();

        }
        public static void show()
        {
             while (i <= 100)
            {
                int num = i % 2;
                if (num == 0)
                {
                    Console.WriteLine("{0}:{1} {2}  ", Thread.CurrentThread.Name, i++, "evenResetEvent");
                    if(i!=1) evenResetEvent.Set(); 
                    oddResetEvent.WaitOne(); //當前線程阻塞
                   
                }
               else
                {  
                    Console.WriteLine("{0}:{1} {2} ", Thread.CurrentThread.Name, i++, "oddResetEvent");
                    //若是此時AutoResetEvent 爲非終止狀態,則線程會被阻止,並等待當前控制資源的線程經過調用 Set 來通知資源可用。不然不會被阻止
                     oddResetEvent.Set();
                    evenResetEvent.WaitOne();
                }
            }
        }

結果以下圖所示: 函數

注意點: <font color=red>不要有一點點點點多餘的evenResetEvent.Set(),他會讓後續的 evenResetEvent.WaitOne();失效.</font>性能

第二種方法Semaphore

此外,咱們利用信號量也能夠實現,信號量是一種內核模式鎖,對性能要求比較高,特殊狀況下才考慮使用,並且要避免在內核模式和用戶模式下頻繁相互切換線程。代碼以下:測試

private static readonly int MaxSize = 1;
        private static int i = 0;
        static Semaphore oddSemaphore = new Semaphore(0, MaxSize);
        static Semaphore evenSemaphore = new Semaphore(0, MaxSize);

        static void Main(string[] args)
        {
            System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
            stopwatch.Start();
            //ThreadStart是個委託
            Thread thread1 = new Thread(new ThreadStart(show));
            thread1.Name = "偶數線程";
            Thread thread2 = new Thread(new ThreadStart(show));
            thread2.Name = "奇數線程";
            thread1.Start();
            thread2.Start();
            thread1.Join();
            stopwatch.Stop();
            Console.WriteLine(stopwatch.Elapsed.TotalMilliseconds);
            Console.Read();
        }

        private static void show()
        {
            if(i==1) evenSemaphore.WaitOne();
            while (i <= 100)
            {
                int num = i % 2;
                if (num == 0)
                {
                    Console.WriteLine("{0}:{1}  {2}    ", Thread.CurrentThread.Name, i++, Thread.CurrentThread.ManagedThreadId);
                    evenSemaphore.Release();
                    oddSemaphore.WaitOne(); //當前線程阻塞
                }
                else
                {
                    Console.WriteLine("{0}:{1}  {2}    ", Thread.CurrentThread.Name, i++, Thread.CurrentThread.ManagedThreadId);
                    //釋放一個偶數信號空位出來;
                    oddSemaphore.Release();
                    evenSemaphore.WaitOne(); //當前線程阻塞
                    //此時已經消耗了一個奇數信號空位
                }
            }
        }

第三種方法,約定每一個線程只幹本身的事

這種方法利用線程池自己就是隊列的方式,即先進先出。測試以後發現性能有降低,可是仍是貼出來供參考。優化

static int threadCount = 2;
        static int count = 0;
        static object cursorLock = new object();
        static void Main(string[] args)
        {
            System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
            stopwatch.Start();
            Task[] arr = new Task[2];
            for (int threadIndex = 0; threadIndex < threadCount; threadIndex++)
            {
                //這兩種方法均可以
                arr[threadIndex] = Task.Factory.StartNew(PrintNum, threadIndex);
            }
            Task.WaitAll(arr);
            stopwatch.Stop();
            Console.WriteLine(stopwatch.Elapsed.TotalMilliseconds);
            Console.Read();
        }


        private static void PrintNum(object num)
        {
            bool isOk = false;
            while (!isOk)
            {
                lock (cursorLock)
                {
                    int index = count % 2;
                    if (count>100)
                    {
                        isOk = true;
                    }
                    else if (index == (int)num)
                    {
                        if (index == 0) Console.WriteLine("{0}:{1} {2} ", "偶數線程", Thread.CurrentThread.ManagedThreadId, count++);
                        else Console.WriteLine("{0}:{1} {2} ", "奇數線程", Thread.CurrentThread.ManagedThreadId, count++);
                    }
                }
            }
        }

結果以下: spa

第四種方法 Mutex

private static int i = 0;
        static Mutex mutex = new Mutex();
     
        static void Main(string[] args)
        {
            System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
            stopwatch.Start();
            //ThreadStart是個委託
            Thread thread1 = new Thread(new ParameterizedThreadStart(show));
            thread1.Name = "偶數線程";
            Thread thread2 = new Thread(new ParameterizedThreadStart(show));
            thread2.Name = "奇數線程";
            thread1.Start(0);
            thread2.Start(1);
            thread2.Join();
            stopwatch.Stop();
            Console.WriteLine(stopwatch.Elapsed.TotalMilliseconds);
            Console.Read();
        }
        /// <summary>
        /// Mutex的釋放與鎖定 都只能在同一個線程中執行
        /// </summary>
        private static void show(object index)
        {
            while (i <= 100)
            {
                mutex.WaitOne();
                int num = i % 2;
                if (num == (int)index&&i<=100)
                {
                    Console.WriteLine("{0}:{1}  {2}  ", Thread.CurrentThread.Name, i++, Thread.CurrentThread.ManagedThreadId);
                }
                mutex.ReleaseMutex();
            }
           
        }

有關概念資料 https://www.cnblogs.com/michaelxu/archive/2008/09/20/1293716.html.net

原文出處:https://www.cnblogs.com/zhan520g/p/11388591.htmlpwa

相關文章
相關標籤/搜索