菜鳥學習并行編程,參考《C#并行編程高級教程.PDF》,如有錯誤,歡迎指正。
C#并行編程-相關概念
C#并行編程-Parallel
C#并行編程-Task
C#并行編程-并發集合
C#并行編程-線程同步原語
C#并行編程-PLINQ:聲明式數據并行
背景
有時候必須訪問變量、實例、方法、屬性或者結構體,而這些并沒有準備好用于并發訪問,或者有時候需要執行部分代碼,而這些代碼必須單獨運行,這是不得不通過將任務分解的方式讓它們獨立運行。
當任務和線程要訪問共享的數據和資源的時候,您必須添加顯示的同步,或者使用原子操作或鎖。
之前的.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; } }View Code
如代碼所示,在串行代碼中,雖然任務是有序進行,但是等待的時間很長,因為只是在一個處理器下進行處理,如下圖所示:
而采用并發處理中,使用Barrier,不僅保證了任務的有序進行,還在性能損耗上得到了最大程度的降低,如下圖
ContinueWhenAll 提供一組任務完成后的延續方法
新聞熱點
疑難解答