C# 创建线程的多种方式之 Parallel类 基础知识

Parallel 类定义了并行运行的静态For(), Foreach(), Invoke()方法, 其中For(), Foreach() 多次调用同一个方法,方法返回值均为ParallelLoopResult,而Invoke()可同时调用多个不同的方法,无返回值。

For(), Foreach()方法有很多重载方法,以参数最多的For<TLocal>(Int32, Int32, ParallelOptions, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) 进行说明,其他类似。以下参数含义说明来自MSDN,

类型参数
TLocal: The type of the thread-local data.     //局部变量,在
参数
fromInclusive Int32  : The start index, inclusive.       //循环起始索引
toExclusive Int32 The end index, exclusive.           //循环结束索引
parallelOptions ParallelOptions An object that configures the behavior of this operation.        //并行执行参数,包括最大并发数量,线程取消操作通知,任务调度器
localInit Func<TLocal> : The function delegate that returns the initial state of the local data for each task.     //每个线程初始化函数的委托
body Func<Int32,ParallelLoopState,TLocal,TLocal> : The delegate that is invoked once per iteration.           //循环运行的函数体,ParallelLoopState  类可以调用Stop()和Break()方法,停止循环运行
localFinally Action<TLocal> : The delegate that performs a final action on the local state of each task.          //每个线程结束处理函数的委托 为了方便观察,写了以下例子:
        static void Main(string[] args)
        {
            Console.WriteLine("Main Start....");
            ParallelOptions po = new ParallelOptions();      //并行执行参数类
            po.MaxDegreeOfParallelism = 4;        //最大并发数量
            int result=0;
            ParallelLoopResult pr = Parallel.For<int>(0, 20, po, () =>         //Initial
            {
                Console.WriteLine("taskId {0} Parallel initial.....",Task.CurrentId);     //输出当前任务Id
                return 1;
            },
            (i,ps,v1) =>                                                     //body
            {
                v1 += 1;
                Console.WriteLine("taskId {0} loopIndex{1},{2}", Task.CurrentId, i, v1);       //输出当前任务Id, 循环索引 , 局部变量
                return v1;
            },
            (v1) =>                                                             //finally
            {               
                Interlocked.Add(ref result, v1);                               //原子操作,操作共享变量result
                Console.WriteLine("taskId {0} Parallel finalized.....Result is {1}", Task.CurrentId, v1);    //输出当前任务Id, 局部变量
            });
            Console.WriteLine("Main end....{0}",result);
            Console.ReadLine();
        }

运行结果每次都有点不同,复制了一个运行结果,如下:

taskId 1 Parallel initial.....
taskId 1 loopIndex0,2
taskId 1 loopIndex1,3
taskId 2 Parallel initial.....
taskId 2 loopIndex5,2
taskId 2 loopIndex6,3
taskId 4 Parallel initial.....
taskId 4 loopIndex15,2
taskId 4 loopIndex16,3
taskId 4 loopIndex17,4
taskId 4 loopIndex18,5
taskId 4 loopIndex19,6
taskId 4 loopIndex3,7
taskId 4 loopIndex4,8
taskId 4 loopIndex8,9
taskId 4 loopIndex9,10
taskId 4 loopIndex11,11
taskId 4 loopIndex12,12
taskId 4 loopIndex13,13
taskId 4 loopIndex14,14
taskId 3 Parallel initial.....
taskId 1 loopIndex2,4
taskId 1 Parallel finalized.....Result is 4
taskId 3 loopIndex10,2
taskId 3 Parallel finalized.....Result is 2
taskId 2 loopIndex7,4
taskId 2 Parallel finalized.....Result is 4
taskId 4 Parallel finalized.....Result is 14
Main end....24

Paralle.For() 实际上是调用了线程池,至于调用线程的个数是不固定的,所以会导致每次运行显示不同。从上面的结果可以看出,此次运行共调用了4个线程,相应的 localInit 和 localFinally 也都调用了4次(注意: localInit 和 localFinally是指线程的初始化和结束处理,而不是循环)。从loopIndex的显示可以看出,循环执行是无顺序的。从局部变量v1的显示,可以看出局部变量在同一个线程中会由上次迭代传递给下次迭代,如上taskId 4 的局部变量一直在递增。

循环体中止:body方法参数 ParallelLoopState  类,调用stop() 或 break() 即可,但这两中方式有些不同。

Stop(): 停止当前迭代,阻止其他还未开始的迭代,已经在执行的迭代不受影响. 可以检测IsStop属性来判断是否调用Stop()方法,停止已经执行的迭代;   -- 推荐使用此方法停止

Break(): 阻止当前其他高于当前循环索引的迭代,已经在执行的不受影响。其实可以在每次迭代中检测ShouldExitCurrentIteration 属性,若为真,说明其他迭代调用了Break(),紧接着马上检测LowestBreakIteration 属性,如果当前循环索引小于该值,则立马return。

Parallel.Foreach() 的使用基本类似,ForEach<TSource,TLocal>(IEnumerable<TSource>, Func<TLocal>, Func<TSource,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) ,

        static void Main(string[] args)
        {
            Console.WriteLine("Main Start....");
            List<int> numList = new List<int>();
            numList.AddRange(new int[]{0,1,2,3,4,5,6,7,8,9});
            int result=0;
            ParallelLoopResult pr = Parallel.ForEach<int, int>(numList, () =>         //Initial
            {
                Console.WriteLine("taskId {0} Parallel initial.....",Task.CurrentId);     //输出当前任务Id
                return 1;
            },
            (num,ps,i,v1) =>                                                     //body
            {
                v1 += num;
                Console.WriteLine("taskId {0} loopIndex{1},{2}", Task.CurrentId, i, v1);       //输出当前任务Id, 循环索引 , 局部变量
                return v1;
            },
            (v1) =>                                                             //finally
            {               
                Interlocked.Add(ref result, v1);                               //原子操作,操作共享变量result
                Console.WriteLine("taskId {0} Parallel finalized.....Result is {1}", Task.CurrentId, v1);    //输出当前任务Id, 局部变量
            });
            Console.WriteLine("Main end....{0}",result);
            Console.ReadLine();
        }

运行结果:

Main Start....
taskId 1 Parallel initial.....
taskId 2 Parallel initial.....
taskId 3 Parallel initial.....
taskId 4 Parallel initial.....
taskId 3 loopIndex4,5
taskId 3 loopIndex5,10
taskId 3 loopIndex7,17
taskId 3 loopIndex8,25
taskId 3 loopIndex9,34
taskId 3 loopIndex1,35
taskId 3 loopIndex3,38
taskId 3 Parallel finalized.....Result is 38
taskId 1 loopIndex0,1
taskId 1 Parallel finalized.....Result is 1
taskId 2 loopIndex2,3
taskId 2 Parallel finalized.....Result is 3
taskId 4 loopIndex6,7
taskId 4 Parallel finalized.....Result is 7
Main end....49

从结果分析基本上与For() 类似,但Foreach() 里还有一个 Partitioner 参数, 分区器。 通过分区器可以控制并发线程的数目,以及每个线程运行的迭代次数。

最后还有Invoke (params Action[] actions), 参数为params 可变参数,具体应用如下:

        static void Main(string[] args)
        {
            Console.WriteLine("Main Start....");
            Stopwatch sw = new Stopwatch();
            sw.Start();
            Parallel.Invoke(() =>
            {
                Console.WriteLine("TaskId{0} Func1 runs...",Task.CurrentId);
                Thread.Sleep(3000);
            },
            delegate
            {
                Console.WriteLine("TaskId{0} Func2 runs...", Task.CurrentId);
                Thread.Sleep(3000);
            },
            delegate
            {
                Console.WriteLine("TaskId{0} Func3 runs...", Task.CurrentId);
                Thread.Sleep(3000);
            });
            sw.Stop();
            Console.WriteLine("Main end....{0}",sw.Elapsed);
            Console.ReadLine();
        }

运行结果:

Main Start....
TaskId1 Func1 runs...
TaskId2 Func2 runs...
TaskId3 Func3 runs...
Main end....00:00:03.0503290

从结果看出,调用了3个不同的线程同时执行,虽然每个方法都延迟了3000ms,但总共运行时间也差不多是3000ms。 

 

转载于:https://www.cnblogs.com/change-myself/p/11149392.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
内容简介 您想淋漓尽致地发挥多核计算机系统的处理能力吗?《C#并行编程高级教程:精通NET 4 Parallel Extensions》将帮助您实现这一夙愿。这本精品书籍浓墨重彩地描述如何使用C# 4、Visual Studio 2010和.NET Framework 4高效地创建基于任务的并行应用程序,详细讲述最新的单指令、多数据流指令和向量化等并行编程技术,介绍现代并行库,讨论如何珠联璧合地使用高级Intel工具与C#,并指导您巧妙使用新引入的轻型协调结构来开发自己的解决方案并解决最棘手的并发编程问题。 主要内容 ◆介绍如何基于新Task Parallel Library和.NET 4设计稳定的可扩展并行应用程序。 ◆讲解命令式数据并行、命令式任务并行、并发集合以及协调数据结构。 ◆描述PLINQ高级声明式数据并行。 ◆讨论如何使用新的Visual Studio 2010并行调试功能来调试匿名方法、任务和线程。 ◆演示如何对数据源进行分区,以便在不同任务和线程之间合理分配工作负荷。 作者简介 Caston C.Hillar是一位独立软件咨询师,自1997年起便一直从事并行编程、多处理器和多核领域的研究,Gaston拥有使用C#和.NET Framework来设计和开发各种复杂并行解决方案的丰富经验,曾于2009年荣膺Intel Black Belt Software Developer奖。 目录 第1章 基于任务的程序设计 1.1 使用共享内存的多核系统 1.1.1 共享内存多核系统与分布式内存系统之间的区别 1.1.2 并行程序设计和多核程序设计 1.2 理解硬件线程和软件线程 1.3 理解Amdahl法则 1.4 考虑Gustafson法则 1.5 使用轻量级并发模型 1.6 创建成功的基于任务的设计 1.6.1 以并发的思想指导设计 1.6.2 理解交错并发、并发和并行之间的区别 1.6.3 并行化任务 1.6.4 尽量减少临界区 1.6.5 理解多核并行程序的设计原则 1.7 为NUMA架构和更高的可扩展性做好准备 1.8 判断是否适合并行化 1.9 小结 第2章 命令式数据并行 2.1 加载并行任务 2.1.1 System.Threading.Tasks.Parallel 2.1.2 Parallel.Invoke 2.2 将串行代码转换为并行代码 2.2.1 检测可并行化的热点 2.2.2 测量并行执行的加速效果 2.2.3 理解并发执行 2.3 循环并行化 2.3.1 Parallel.For 2.3.2 Parallel.ForEach 2.3.3 从并行循环中退出 2.4 指定并行度 2.4.1 ParallelOptions 2.4.2 计算硬件线程 2.4.3 逻辑内核并不是物理内核 2.5 通过甘特图检测临界区 2.6 小结 第3章 命令式任务并行 3.1 创建和管理任务 3.1.1 System.Threading.Tasks.Task 3.1.2 理解Task状态和生命周期 3.1.3 通过使用任务来对代码进行并行化 3.1.4 等待任务完成 3.1.5 忘记复杂的线程 3.1.6 通过取消标记取消任务 3.1.7 从任务返回值 3.1.8 TaskCreationOptions 3.1.9 通过延续串联多个任务 3.1.10 编写适应并发和并行的代码 3.2 小结 第4章 并发集合 4.1 理解并发集合提供的功能 4.1.1 System.Collections.Concurrent 4.1.2 ConcurrentQueue 4.1.3 理解并行的生产者-消费者模式 4.1.4 ConcurrentStack 4.1.5 将使用数组和不安全集合的代码转换为使用并发集合的代码 4.1.6 ConcurrentBag 4.1.7 IProducerConsumerCollection 4.1.8 BlockingCollection 4.1.9 ConcurrentDictionary 4.2 小结 第5章 协调数据结构 5.1 通过汽车和车道理解并发难题 5.1.1 非预期的副作用 5.1.2 竞争条件 5.1.3 死锁 5.1.4 使用原子操作的无锁算法 5.1.5 使用本地存储的无锁算法 5.2 理解新的同步机制 5.3 使用同步原语 5.3.1 通过屏障同步并发任务 5.3.2 屏障和ContinueWhenAll 5.3.3 在所有的参与者任务中捕捉异常 5.3.4 使用超时 5.3.5 使用动态数目的参与者 5.4 使用互斥锁 5.4.1 使用Monitor 5.4.2 使用锁超时 5.4.3 将代码重构为避免使用锁 5.5 将自旋锁用作互斥锁原语 5.5.1 使用超时 5.5.2 使用基于自旋的等待 5.5.3 自旋和处理器出让 5.5.4 使用volatile修饰符 5.6 使用轻量级的手动重置事件 5.6.1 使用ManualResetEventSlim进行自旋和等待 5.6.2 使用超时和取消 5.6.3 使用ManualResetEvent 5.7 限制资源的并发访问 5.7.1 使用SemaphoreSlim 5.7.2 使用超时和取消 5.7.3 使用 Semaphore 5.8 通过CountdownEvent简化动态fork和join场景 5.9 使用原子操作 5.10 小结 第6章 PLINQ:声明式数据并行 6.1 从LINQ转换到PLINQ 6.1.1 ParallelEnumerable及其AsParallel方法 6.1.2 AsOrdered和orderby子句 6.2 指定执行模式 6.3 理解PLINQ中的数据分区 6.4 通过PLINQ执行归约操作 6.5 创建自定义的PLINQ聚合函数 6.6 并发PLINQ任务 6.7 取消PLINQ 6.8 指定所需的并行度 6.8.1 WithDegreeOfParallelism 6.8.2 测量可扩展性 6.9 使用ForAll 6.9.1 foreach和ForAll的区别 6.9.2 测量可扩展性 6.10 通过WithMergeOptions配置返回结果的方式 6.11 处理PLINQ抛出的异常 6.12 使用PLINQ执行MapReduce算法 6.13 使用PLINQ设计串行多步操作 6.14 小结 第7章 Visual Studio 2010的任务调试能力 7.1 充分利用多显示器的支持 7.2 理解并行任务调试器窗口 7.3 查看Parallel Stacks图 …… 第8章 线程池 第9章 异步编程模型 第10章 并行测试和调优 第11章 向量化、SIMD指令以及其他并行库 附录A .NET 4中与并行相关的图 附录B 并发UML模型 附录C Parallel Extensions Extras

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值