菜鸟学习并行编程,参考《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 提供一组任务完成后的延续方法
新闻热点
疑难解答