程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> C#並行編程概述:線程同步原語

C#並行編程概述:線程同步原語

編輯:關於C#

背景

有時候必須訪問變量、實例、方法、屬性或者結構體,而這些並沒有准備好用於並發訪問,或者有時候需要執行部分代碼,而這些代碼必須單獨運行,這是不得不通過將任務分解的方式讓它們獨立運行。

當任務和線程要訪問共享的數據和資源的時候,您必須添加顯示的同步,或者使用原子操作或鎖。

之前的.NET Framework提供了昂貴的鎖機制以及遺留的多線程模型,新的數據結構允許細粒度的並發和並行化,並且降低一定必要的開銷,這些數據結構稱為輕量級同步原語。

這些數據結構在關鍵場合下能夠提供更好的性能,因為它們能夠避免昂貴的鎖機制,如果在等待時間不短的情況下使用它們,這些原語會增加額外的開銷。

如果您需要特定的執行順序,可以通過添加顯示同步來實現。

同步原語

.NET Framework 4在現在的System.Threading命名空間中提供了6個同步原語,通過這個命名空間可以訪問遺留的線程類、類型和枚舉,還提供了新的基於任務的編程模型及特定情形緊密相關的數據結構

Barrier 使多個任務能夠采用並行方式依據某種算法在多個階段中協同工作 通過屏障

CountdownEvent 表示在計數變為0時處於有信號狀態的同步基元 通過信號機制

ManualResetEventSlim 允許很多任務等待直到另一個任務手工發出事件句柄,當預計等待時間很短的時候,ManualResetEventSlim 的性能比對應的重量級ManualResetEvent的性能要高。通過信號機制

SemaphoreSlim 限制對可同時訪問資源或資源池的線程數,比對應的Semaphore性能要高 通過信號機制

SpinLock 提供一個相互排斥鎖基元,在該基元中,嘗試獲得鎖的線程將在重復檢查的循環中等待,直至該鎖變為可用為止。

SpinWait 提供對基於自旋的等待的支持。

通過屏障同步並發任務 Barrier

當在需要一組任務並行地運行一連串的階段,但是每一個階段都要等待其他任務完成前一階段之後才能開始時,您可以通過使用Barrier類的實例來同步這一類協同工作,通過屏障

下面貼代碼方便大家理解,如有問題,請指正,詳情見注釋:

class Program
    {
        private static Task[] _CookTasks { get; set; }
        private static Barrier _barrier { get; set; }
        /*獲取當前計算機處理器數*/
        private static int _particpants = Environment.ProcessorCount;
        /*  coder:釋迦苦僧  
         *  代碼中 展示煮飯的步驟   1.打水  2.淘米 3.放入鍋中 4.蓋上鍋蓋 5.生火煮飯 
         */
        static void Main(string[] args)
        {
            Console.WriteLine("定義{0}個人煮飯3次", _particpants);
            _CookTasks = new Task[_particpants];
            _barrier = new Barrier(_particpants, (barrier) =>
            {
                Console.WriteLine("當前階段:{0}", barrier.CurrentPhaseNumber);
            });
            Stopwatch swTask1 = new Stopwatch();
            swTask1.Start();
            /*定義N個人*/
            for (int cook_person = 0; cook_person < _particpants; cook_person++)
            {
                _CookTasks[cook_person] = Task.Factory.StartNew((num) =>
                {
                    int index = Convert.ToInt32(num);
                    /*每個人煮3次飯*/
                    for (int cook_count = 0; cook_count < 3; cook_count++)
                    {
                        CookStepTask1(index, cook_count);
                        CookStepTask2(index, cook_count);
                        CookStepTask3(index, cook_count);
                        CookStepTask4(index, cook_count);
                        CookStepTask5(index, cook_count);
                    }
                }, cook_person);
            }
    
            /*ContinueWhenAll 提供一組任務完成後 延續方法*/
            var finalTask = Task.Factory.ContinueWhenAll(_CookTasks, (tasks) =>
            {
                /*等待任務完成*/
                Task.WaitAll(_CookTasks);
                swTask1.Stop();
                Console.WriteLine("采用並發 {1}個人煮3次飯耗時:{0}", swTask1.ElapsedMilliseconds, _particpants);
                /*釋放資源*/
                _barrier.Dispose();
            });
            Thread.Sleep(4000);
            Stopwatch swTask = new Stopwatch();
            swTask.Start();
    
            /*定義N個人*/
            for (int cook_person = 0; cook_person < _particpants; cook_person++)
            {
                /*每個人煮3次飯*/
                for (int cook_count = 0; cook_count < 3; cook_count++)
                {
                    CookStep1(cook_person, cook_count); CookStep2(cook_person, cook_count); CookStep3(cook_person, cook_count); CookStep4(cook_person, cook_count); CookStep5(cook_person, cook_count);
                }
            }
            swTask.Stop();
            Console.WriteLine("不采用並發 {1}個人煮3次飯耗時:{0}", swTask.ElapsedMilliseconds, _particpants);
            Thread.Sleep(2000);
    
    
            Console.ReadLine();
        }
        /*1.打水*/
        private static void CookStepTask1(int pesron_index, int index)
        {
            Console.WriteLine("{0} 第{1}次 打水... 耗時2分鐘", pesron_index, index);
            Thread.Sleep(200);
            /*存在線程暫停 所以需要將 _barrier.SignalAndWait();放在方法中 */
            _barrier.SignalAndWait();
        }
    
        /*2.淘米*/
        private static void CookStepTask2(int pesron_index, int index)
        {
            Console.WriteLine("{0} 第{1}次 淘米... 耗時3分鐘", pesron_index, index);
            Thread.Sleep(300);
            /*存在線程暫停 所以需要將 _barrier.SignalAndWait();放在方法中 */
            _barrier.SignalAndWait();
        }
    
        /*3.放入鍋中*/
        private static void CookStepTask3(int pesron_index, int index)
        {
            Console.WriteLine("{0} 第{1}次 放入鍋中... 耗時1分鐘", pesron_index, index);
            Thread.Sleep(100);
            /*存在線程暫停 所以需要將 _barrier.SignalAndWait();放在方法中 */
            _barrier.SignalAndWait();
        }
    
        /*4.蓋上鍋蓋*/
        private static void CookStepTask4(int pesron_index, int index)
        {
            Console.WriteLine("{0} 第{1}次 蓋上鍋蓋... 耗時1分鐘", pesron_index, index);
            Thread.Sleep(100);
            /*存在線程暫停 所以需要將 _barrier.SignalAndWait();放在方法中 */
            _barrier.SignalAndWait();
        }
        /*5.生火煮飯*/
        private static void CookStepTask5(int pesron_index, int index)
        {
            Console.WriteLine("{0} 第{1}次  生火煮飯... 耗時30分鐘", pesron_index, index);
            Thread.Sleep(500);
            /*存在線程暫停 所以需要將 _barrier.SignalAndWait();放在方法中 */
            _barrier.SignalAndWait();
        }
    
        /*1.打水*/
        private static void CookStep1(int pesron_index, int index)
        {
            Console.WriteLine("{0} 第{1}次 打水... 耗時2分鐘", pesron_index, index);
            Thread.Sleep(200);
        }
    
        /*2.淘米*/
        private static void CookStep2(int pesron_index, int index)
        {
            Console.WriteLine("{0} 第{1}次 淘米... 耗時3分鐘", pesron_index, index);
            Thread.Sleep(300);
        }
    
        /*3.放入鍋中*/
        private static void CookStep3(int pesron_index, int index)
        {
            Console.WriteLine("{0} 第{1}次 放入鍋中... 耗時1分鐘", pesron_index, index);
            Thread.Sleep(100);
        }
    
        /*4.蓋上鍋蓋*/
        private static void CookStep4(int pesron_index, int index)
        {
            Console.WriteLine("{0} 第{1}次 蓋上鍋蓋... 耗時1分鐘", pesron_index, index);
            Thread.Sleep(100);
        }
        /*5.生火煮飯*/
        private static void CookStep5(int pesron_index, int index)
        {
            Console.WriteLine("{0} 第{1}次  生火煮飯... 耗時30分鐘", pesron_index, index);
            Thread.Sleep(500);
        }
    }
    
    class Product
    {
        public string Name { get; set; }
        public string Category { get; set; }
        public int SellPrice { get; set; }
    }

如代碼所示,在串行代碼中,雖然任務是有序進行,但是等待的時間很長,因為只是在一個處理器下進行處理,如下圖所示:

URL:http://www.bianceng.cn/Programming/csharp/201410/45755.htm

而采用並發處理中,使用 Barrier,不僅保證了任務的有序進行,還在性能損耗上得到了最大程度的降低,如下圖

ContinueWhenAll 提供一組任務完成後的延續方法

/*ContinueWhenAll 提供一組任務完成後 延續方法*/
            var finalTask = Task.Factory.ContinueWhenAll(_CookTasks, (tasks) =>
            {
                /*等待任務完成*/
                Task.WaitAll(_CookTasks);
                swTask1.Stop();
                Console.WriteLine("采用並發 {1}個人煮3次飯耗時:{0}", swTask1.ElapsedMilliseconds, _particpants);
                /*釋放資源*/
                _barrier.Dispose();
            });

常和超時處理

廢話不多說 直接貼代碼,如有問題請指正:

class Program
    {
        private static Task[] _CookTasks { get; set; }
        private static Barrier _barrier { get; set; }
        /*獲取當前計算機處理器數*/
        private static int _particpants = Environment.ProcessorCount;
        /*  coder:釋迦苦僧  
         *  代碼中 展示煮飯的步驟   1.打水  2.淘米 3.放入鍋中 4.蓋上鍋蓋 5.生火煮飯 
         */
        static void Main(string[] args)
        {
            Console.WriteLine("定義{0}個人煮飯3次", _particpants);
            _CookTasks = new Task[_particpants];
            _barrier = new Barrier(_particpants, (barrier) =>
            {
                Console.WriteLine("當前階段:{0}", barrier.CurrentPhaseNumber);
            });
            Stopwatch swTask1 = new Stopwatch();
            swTask1.Start();
            /*定義N個人*/
            for (int cook_person = 0; cook_person < _particpants; cook_person++)
            {
                _CookTasks[cook_person] = Task.Factory.StartNew((num) =>
                {
                    int index = Convert.ToInt32(num);
                    /*每個人煮3次飯*/
                    for (int cook_count = 0; cook_count < 3; cook_count++)
                    {
                        CookStepTask1(index, cook_count);
                        /*處理等待中的異常 如果等待時間超過300毫秒的話則拋出
                         * 參考方法體1中 模擬了超時操作, 則屏障等待時 如果發現超時 則處理異常
                         */
                        try
                        {
                            /*屏障 等待超過2秒鐘 其執行算法有問題 超時  則拋出異常 記錄信息 提醒開發人員觀察*/
                            if (!_barrier.SignalAndWait(2000))
                            {
                                /*拋出超時異常*/
                                throw new OperationCanceledException("等待超時,拋出異常");
                            }
                        }
                        catch (Exception ex)
                        {
                            /*處理異常*/
                            Console.WriteLine(ex.Message);
                            continue;
                        }
                        CookStepTask2(index, cook_count);
                        _barrier.SignalAndWait();
                        CookStepTask3(index, cook_count);
                        _barrier.SignalAndWait();
                        CookStepTask4(index, cook_count);
                        _barrier.SignalAndWait();
                        CookStepTask5(index, cook_count);
                        _barrier.SignalAndWait();
                    }
                }, cook_person);
            }
    
            /*ContinueWhenAll 提供一組任務完成後 延續方法*/
            var finalTask = Task.Factory.ContinueWhenAll(_CookTasks, (tasks) =>
            {
                foreach (Task task in _CookTasks)
                {
                    if (task.Exception != null)
                    {
                        /*任務執行完成後  輸出所有異常 打印異常報表*/
                        foreach (Exception exception in task.Exception.InnerExceptions)
                        {
                            Console.WriteLine("異常信息:{0}", exception.Message);
                        }
                    }
                }
                /*等待任務完成*/
                Task.WaitAll(_CookTasks);
                swTask1.Stop();
                Console.WriteLine("采用並發 {1}個人煮3次飯耗時:{0}", swTask1.ElapsedMilliseconds, _particpants);
                /*釋放資源*/
                _barrier.Dispose();
    
            });
    
            Console.ReadLine();
        }
        /*1.打水*/
        private static void CookStepTask1(int pesron_index, int index)
        {
            Console.WriteLine("{0} 第{1}次 打水... 耗時2分鐘", pesron_index, index);
    
            /*模擬一個方法體內異常拋出*/
            //throw new Exception("拋出一個代碼異常");
            if (pesron_index == 0)
            {
                /*模擬超時操作*/
                //SpinWait.SpinUntil(() => (_barrier.ParticipantsRemaining == 0), 5000);
                Thread.Sleep(5000);
            }

        } 
        /*2.淘米*/
        private static void CookStepTask2(int pesron_index, int index)
        {
            Console.WriteLine("{0} 第{1}次 淘米... 耗時3分鐘", pesron_index, index);
        }
    
        /*3.放入鍋中*/
        private static void CookStepTask3(int pesron_index, int index)
        {
            Console.WriteLine("{0} 第{1}次 放入鍋中... 耗時1分鐘", pesron_index, index);
        }
    
        /*4.蓋上鍋蓋*/
        private static void CookStepTask4(int pesron_index, int index)
        {
            Console.WriteLine("{0} 第{1}次 蓋上鍋蓋... 耗時1分鐘", pesron_index, index);
        }
        /*5.生火煮飯*/
        private static void CookStepTask5(int pesron_index, int index)
        {
            Console.WriteLine("{0} 第{1}次  生火煮飯... 耗時30分鐘", pesron_index, index);
        }
    }

如代碼所示,在 CookStepTask1 方法體中,我模擬了超時和異常,並在Task任務中,利用Barrier的SignalAndWait方法處理屏障中的超時信息,和Task中異常記錄信息。

URL:http://www.bianceng.cn/Programming/csharp/201410/45755.htm

鎖的特性

互斥和可見性。互斥指的是一次只允許一個線程持有某個特定的鎖,因此可以保證共享數據內容的一致性;

可見性指的是必須確保鎖被釋放之前對共享數據的修改,隨後獲得鎖的另一個線程能夠知道該行為。

參考http://www.cnblogs.com/lucifer1982/archive/2008/03/23/1116981.html

互斥鎖-System.Threading.Monitor

如果有一個臨界區,一次只有一個任務能夠訪問這個臨界區,但是這個臨界區需要被很多任務循環訪問,那麼使用任務延續並不是一個好的選擇,那麼另一種替換方案就是采用互斥鎖原語。

下面已操作字符串為示意,看下不采用鎖,采用傳統的LOCK和采用互斥鎖的區別

不采用任何鎖機制代碼如下:

class Program
    {
        private static Task[] _CookTasks { get; set; }
        private static object o = new object();
        private static StringBuilder AppendStrUnLock = new StringBuilder();
        private static StringBuilder AppendStrLock = new StringBuilder();
        private static StringBuilder AppendStrMonitorLock = new StringBuilder();
        /*獲取當前計算機處理器數*/
        private static int _particpants = Environment.ProcessorCount;
        /*  coder:釋迦苦僧   */
        static void Main(string[] args)
        { 
            _CookTasks = new Task[_particpants];
            Stopwatch swTask1 = new Stopwatch();
            swTask1.Start();
            for (int task_index = 0; task_index < _particpants; task_index++)
            {
                _CookTasks[task_index] = Task.Factory.StartNew((num) =>
                {
                    Parallel.For(1, 1000, (i) =>
                    {
                        string str = "append message " + i;
                        AppendStrUnLock.Append(str);
                    });
                }, task_index);
            }
    
            /*ContinueWhenAll 提供一組任務完成後 延續方法*/
            var finalTask = Task.Factory.ContinueWhenAll(_CookTasks, (tasks) =>
            { 
                /*等待任務完成*/
                Task.WaitAll(_CookTasks);
                swTask1.Stop();
                Console.WriteLine("不采用Lock操作,字符串長度:{0},耗時:{1}", AppendStrUnLock.Length, swTask1.ElapsedMilliseconds);
                /*釋放資源*/
            });
    
            Console.ReadLine();
        }
    }

采用互斥鎖代碼下:

class Program
    {
        private static Task[] _CookTasks { get; set; }
        private static object o = new object();
        private static StringBuilder AppendStrUnLock = new StringBuilder();
        private static StringBuilder AppendStrLock = new StringBuilder();
        private static StringBuilder AppendStrMonitorLock = new StringBuilder();
        /*獲取當前計算機處理器數*/
        private static int _particpants = Environment.ProcessorCount;
        /*  coder:釋迦苦僧   */
        static void Main(string[] args)
        { 
            _CookTasks = new Task[_particpants];
            Stopwatch swTask1 = new Stopwatch();
            swTask1.Start();
            for (int task_index = 0; task_index < _particpants; task_index++)
            {
                _CookTasks[task_index] = Task.Factory.StartNew((num) =>
                {
                    Parallel.For(1, 1000, (i) =>
                    {
                        string str = "append message " + i; 
                        bool lockTaken = false;
                        try
                        {
                            Monitor.Enter(o, ref lockTaken);
                            AppendStrMonitorLock.Append(str);
                        }
                        finally
                        {
                            if (lockTaken)
                                Monitor.Exit(o);
                        }
                    });
                }, task_index);
            }
    
            /*ContinueWhenAll 提供一組任務完成後 延續方法*/
            var finalTask = Task.Factory.ContinueWhenAll(_CookTasks, (tasks) =>
            { 
                /*等待任務完成*/
                Task.WaitAll(_CookTasks);
                swTask1.Stop();
                Console.WriteLine("不采用Lock操作,字符串長度:{0},耗時:{1}", AppendStrMonitorLock.Length, swTask1.ElapsedMilliseconds);
                /*釋放資源*/
            });
    
            Console.ReadLine();
        }
    }

URL:http://www.bianceng.cn/Programming/csharp/201410/45755.htm

System.Threading.Monitor 類通過使用互斥鎖提供了對象的同步訪問機制,使用Lock關鍵字的等價代碼使用起來更加簡潔,不需要額外的異常捕獲和處理代碼。

但是System.Threading.Monitor好處是提供了些其他的方法(Lock中卻沒有),通過這些方法可以對鎖的過程有更多的控制。需要注意的是 Lock關鍵字和System.Threading.Monitor類仍然是提供互斥訪問的首選方法,不過在某些情形下,其他互斥鎖原語可能會提供更好的性能和更小的開銷,如SpinLock,Lock和System.Threading.Monitor類智能鎖定對象,即引用類型。

鎖超時

在多任務中,很多任務試圖獲得鎖從而進入臨界區,如果其中一個參與者不能釋放鎖,那麼其他所有的任務都要在Monitor.Enter的方法內永久的等待下去。Monitor.TryEnter方法則提供了超時機制,如代碼所示:

class Program
    {
        private static Task[] _CookTasks { get; set; }
        private static object o = new object();
        private static StringBuilder AppendStrUnLock = new StringBuilder();
        private static StringBuilder AppendStrLock = new StringBuilder();
        private static StringBuilder AppendStrMonitorLock = new StringBuilder();
        /*獲取當前計算機處理器數*/
        private static int _particpants = Environment.ProcessorCount;
        /*  coder:釋迦苦僧   */
        static void Main(string[] args)
        {
            _CookTasks = new Task[_particpants];
            Stopwatch swTask1 = new Stopwatch();
            swTask1.Start();
            for (int task_index = 0; task_index < _particpants; task_index++)
            {
                _CookTasks[task_index] = Task.Factory.StartNew((num) =>
                {
                    try
                    {
                        Parallel.For(1, 200000, (i) =>
                        {
                            string str = "append message " + i;
                            bool lockTaken = false;
                            try
                            {
                                Monitor.TryEnter(o, 2000, ref lockTaken);
                                if (!lockTaken)
                                {
                                    throw new  OperationCanceledException("鎖超時....");
                                }
                                if (i == 2)
                                {
                                    Thread.Sleep(40000);
                                }
                                AppendStrMonitorLock.Append(str);
                            }
                            catch (Exception ex)
                            {
                                throw ex;
                            }
                            finally
                            {
                                if (lockTaken)
                                    Monitor.Exit(o);
                            }
                        });
                    }
                    catch (Exception ex)
                    {
                        throw ex;
                    }
    
                }, task_index);
            }
    
            /*ContinueWhenAll 提供一組任務完成後 延續方法*/
            var finalTask = Task.Factory.ContinueWhenAll(_CookTasks, (tasks) =>
            {
                /*等待任務完成*/
                Task.WaitAll(_CookTasks);
                swTask1.Stop();
                foreach (Task task in _CookTasks)
                {
                    if (task.Exception != null)
                    {
                        /*任務執行完成後  輸出所有異常 打印異常報表*/
                        foreach (Exception exception in task.Exception.InnerExceptions)
                        {
                            Console.WriteLine("異常信息:{0}", exception.Message);
                        }
                    }
                }
    
                Console.WriteLine("不采用Lock操作,字符串長度:{0},耗時:{1}", AppendStrMonitorLock.Length, swTask1.ElapsedMilliseconds);
                /*釋放資源*/
            });
    
            Console.ReadLine();
        }
    }

需要注意,上述代碼中,異常並沒有被捕捉到,因此每一個不能獲得鎖的任務都會出錯退出並停止執行。

System.Threading.Monitor類還提供了以下三個方法,大家可以參考MSND:

在實際的編程中需要注意的是:不要將SpinLock聲明為只讀字段,如果聲明為只讀字段,會導致每次調用都會返回一個SpinLock新副本,在多線程下,每個方法都會成功獲得鎖,而受到保護的臨界區不會按照預期進行串行化。

volatile

URL:http://www.bianceng.cn/Programming/csharp/201410/45755.htm

volatile關鍵字能夠保證;當這個共享變量被不同線程訪問和更新且沒有鎖和原子操作的時候,最新的值總能在共享變量中表示出來。

volatile變量可以看作是“輕量級lock”。當出於簡單編碼和可伸縮性考慮時,我們可能會選擇使用volatile變量而不是鎖機制。某些情況下,如果讀操作遠多於寫操作,也會比鎖機制帶來更高性能。

volatile變量具有“lock”的可見性,卻不具備原子特性。也就是說線程能夠自動發現volatile變量的最新值。volatile變量可以實現線程安全,但其應用有限。使用volatile變量的主要原因在於它使用非常簡單,至少比使用鎖機制要簡單的多;其次便是性能原因了,某些情況下,它的性能要優於鎖機制。此外,volatile操作不會造成阻塞。

參考:http://www.cnblogs.com/lucifer1982/archive/2008/03/23/1116981.html 大家可以看下 寫的不錯

ManualResetEventSlim

ManualResetEventSlim通過封裝手動重置事件等待句柄提供了自旋等待和內核等待的組合。您可以使用這個類的實例在任務直接發送信息,並等待事件的發送。通過信號機制通知任務開始其工作。

其Set 方法將事件狀態設置為有信號,從而允許一個或多個等待該事件的線程繼續。 其 Wait()方法 阻止當前線程,直到設置了當前 ManualResetEventSlim 為止。

如果需要跨進程或者跨AppDomain的同步,那麼就必須使用ManualResetEvent,而不能使用ManualResetEventSlim。

using System;
using System.Threading;
using System.Threading.Tasks;
class MRESDemo
{
    static void Main()
    {
        MRES_SetWaitReset();
    }
    static void MRES_SetWaitReset()
    {
        ManualResetEventSlim mres1 = new ManualResetEventSlim(false);
    
        var observer = Task.Factory.StartNew(() =>
        {
            Console.WriteLine("阻塞當前線程,使 mres1 處於等待狀態...!");
            mres1.Wait();
            while (true)
            {
                if (mres1.IsSet)
                {
                    /*等待 mres1 Set()信號 當有信號時 在執行後面代碼*/
                    Console.WriteLine("得到mres1信號,執行後續代碼....!");
                }
                Thread.Sleep(100);
            }
    
        });
    
        Thread.Sleep(2000);
        Console.WriteLine("取消mres1等待狀態");
        mres1.Set();
        Console.WriteLine("當前信號狀態:{0}", mres1.IsSet);
        Thread.Sleep(300);
        mres1.Reset();
        Console.WriteLine("當前信號狀態:{0}", mres1.IsSet);
        Thread.Sleep(300);
        mres1.Set();
        Console.WriteLine("當前信號狀態:{0}", mres1.IsSet);
        Thread.Sleep(300);
        mres1.Reset();
        Console.WriteLine("當前信號狀態:{0}", mres1.IsSet);
    
        observer.Wait();
        mres1.Dispose();
        Console.ReadLine();
    }
}

SemaphoreSlim

有時候,需要對訪問一個紫雲或者一個資源池的並發任務或者線程的數量做限制時,采用System.Threading.SemaphoreSlim類非常有用。

該了表示一個Windows內核信號量對象,如果等待的時間非常短,System.Threading.SemaphoreSlim類帶來的額外開銷會更少,而且更適合對任務處理,System.Threading.SemaphoreSlim提供的計數信號量沒有使用Windows內核的信號量。

計數信號量:通過跟蹤進入和離開任務或線程來協調對資源的訪問,信號量需要知道能夠通過信號量協調機制所訪問共享資源的最大任務數,然後,信號量使用了一個計數器,根據任務進入或離開信號量控制區對計數器進行加減。

需要注意的是:信號量會降低可擴展型,而且信號量的目的就是如此。SemaphoreSlim實例並不能保證等待進入信號量的任務或線程的順序。

下面貼代碼,方便大家理解:

using System;
using System.Threading;
using System.Threading.Tasks;
    
class MRESDemo
{
    /*code:釋迦苦僧*/
    static void Main()
    {
        SemaphoreSlim ss = new SemaphoreSlim(3); // 創建SemaphoreSlim 初始化信號量最多計數為3次
        Console.WriteLine("創建SemaphoreSlim 初始化信號量最多計數為{0}次", ss.CurrentCount);
    
        // Launch an asynchronous Task that releases the semaphore after 100 ms
        Task t1 = Task.Factory.StartNew(() =>
        {
            while (true)
            {
                /*阻止當前線程,直至它可進入 SemaphoreSlim 為止。*/
                /*阻塞當前任務或線程,直到信號量幾首大於0時它才能進入信號量*/
                ss.Wait();
                Console.WriteLine("允許進入 SemaphoreSlim 的線程的數量:{0}", ss.CurrentCount);
                Thread.Sleep(10); 
            }
        });
    
        Thread.Sleep(3000);
        /*當前Task只能進入3次*/
        /*退出一次信號量  並遞增信號量的計數*/
        Console.WriteLine("退出一次信號量  並遞增信號量的計數");
        ss.Release();
    
        Thread.Sleep(3000);
        /*退出3次信號量  並遞增信號量的計數*/
        Console.WriteLine("退出三次信號量  並遞增信號量的計數");
        ss.Release(3);
    
        /*等待任務完成*/
        Task.WaitAll(t1); 
    
        /*釋放*/
        ss.Dispose();
        Console.ReadLine();
    }
}

CountdownEvent

有時候,需要對數目隨時間變化的任務進行跟蹤,CountdownEvent是一個非輕量級的同步原語,與Task.WaitAll或者TaskFactory.ContinueWhenAll 等待其他任務完成執行而運行代碼相比,CountdownEvent的開銷要小得多。

CountdownEvent實例帶有一個初始的信號計數,在典型的fork/join場景下,每當一個任務完成工作的時候,這個任務都會發出一個CountdownEvent實例的信號,並將其信號計數遞減1,調用CountdownEvent的wait方法的任務將會阻塞,直到信號計數達到0.

下面貼代碼,方便大家理解:

class MRESDemo
{
    /*code:釋迦苦僧*/
    static void Main()
    {
        CountdownEvent cde = new CountdownEvent(3); // 創建SemaphoreSlim 初始化信號量最多計數為3次 
        Console.WriteLine(" InitialCount={0}, CurrentCount={1}, IsSet={2}", cde.InitialCount, cde.CurrentCount, cde.IsSet);
    
        // Launch an asynchronous Task that releases the semaphore after 100 ms
        Task t1 = Task.Factory.StartNew(() =>
        {
            while (true)
            {
                Thread.Sleep(1000);
                if (!cde.IsSet)
                {
                    cde.Signal();
                    Console.WriteLine(" InitialCount={0}, CurrentCount={1}, IsSet={2}", cde.InitialCount, cde.CurrentCount, cde.IsSet);
                }
            }
        }); 
        cde.Wait();
        /*將 CurrentCount 重置為 InitialCount 的值。*/
        Console.WriteLine("將 CurrentCount 重置為 InitialCount 的值。");
        cde.Reset();
    
        cde.Wait();
        /*將 CurrentCount 重置為 5*/
        Console.WriteLine("將 CurrentCount 重置為 5");
        cde.Reset(5);
        cde.AddCount(2);
    
        cde.Wait();
        /*等待任務完成*/
        Task.WaitAll(t1);
        Console.WriteLine("任務執行完成");
        /*釋放*/
        cde.Dispose();
        Console.ReadLine();
    }
}

URL:http://www.bianceng.cn/Programming/csharp/201410/45755.htm

class MRESDemo
{
    /*code:釋迦苦僧*/
    static void Main()
    {
        CountdownEvent cde = new CountdownEvent(3); // 創建SemaphoreSlim 初始化信號量最多計數為3次 
        Console.WriteLine(" InitialCount={0}, CurrentCount={1}, IsSet={2}", cde.InitialCount, cde.CurrentCount, cde.IsSet);
        /*創建任務執行計數*/
        Task t1 = Task.Factory.StartNew(() =>
        {
            for (int index = 0; index <= 5; index++)
            {
                /*重置計數器*/
                cde.Reset();
                /*創建任務執行計數*/
                while (true)
                {
                    Thread.Sleep(1000);
                    if (!cde.IsSet)
                    {
                        cde.Signal();
                        Console.WriteLine("第{0}輪計數  CurrentCount={1}", index, cde.CurrentCount);
                    }
                    else
                    {
                        Console.WriteLine("第{0}輪計數完成", index);
                        break;
                    }
                }
                /*等待計數完成*/
                cde.Wait();
            }
        });
        t1.Wait();
        /*釋放*/
        cde.Dispose();
        Console.ReadLine();
    }
}

作者:釋迦苦僧

出處:http://www.cnblogs.com/woxpp

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved