详解c# 并行计算

并行计算部分

沿用微软的写法,System.Threading.Tasks.::.Parallel类,提供对并行循环和区域的支持。 我们会用到的方法有For,ForEach,Invoke。

一、简单使用

首先我们初始化一个List用于循环,这里我们循环10次。(后面的代码都会按这个标准进行循环)

            Program.Data = new List<int>();
            for (int i = 0; i < 10; i++)
            {
                Data.Add(i);
            }

下面我们定义4个方法,分别为for,foreach,并行For,并行ForEach。并测试他们的运行时长。

        /// <summary>
        /// 是否显示执行过程
        /// </summary>
        public bool ShowProcessExecution = false;
        /// <summary>
        /// 这是普通循环for
        /// </summary>
        private void Demo1()
        {
            List<int> data = Program.Data;
            DateTime dt1 = DateTime.Now;
            for (int i = 0; i < data.Count; i++)
            {
                Thread.Sleep(500);
                if (ShowProcessExecution)
                    Console.WriteLine(data[i]);
            }
            DateTime dt2 = DateTime.Now;
            Console.WriteLine("普通循环For运行时长:{0}毫秒。", (dt2 - dt1).TotalMilliseconds);
        }
        /// <summary>
        /// 这是普通循环foreach
        /// </summary>
        private void Demo2()
        {
            List<int> data = Program.Data;
            DateTime dt1 = DateTime.Now;
            foreach (var i in data)
            {
                Thread.Sleep(500);
                if (ShowProcessExecution)
                    Console.WriteLine(i);
            }
            DateTime dt2 = DateTime.Now;
            Console.WriteLine("普通循环For运行时长:{0}毫秒。", (dt2 - dt1).TotalMilliseconds);
        }
        /// <summary>
        /// 这是并行计算For
        /// </summary>
        private void Demo3()
        {
            List<int> data = Program.Data;
            DateTime dt1 = DateTime.Now;
            Parallel.For(0, data.Count, (i) =>
            {
                Thread.Sleep(500);
                if (ShowProcessExecution)
                    Console.WriteLine(data[i]);
            });
            DateTime dt2 = DateTime.Now;
            Console.WriteLine("并行运算For运行时长:{0}毫秒。", (dt2 - dt1).TotalMilliseconds);
        }
        /// <summary>
        /// 这是并行计算ForEach
        /// </summary>
        private void Demo4()
        {
            List<int> data = Program.Data;
            DateTime dt1 = DateTime.Now;
            Parallel.ForEach(data, (i) =>
            {
                Thread.Sleep(500);
                if (ShowProcessExecution)
                    Console.WriteLine(i);
            });
            DateTime dt2 = DateTime.Now;
            Console.WriteLine("并行运算ForEach运行时长:{0}毫秒。", (dt2 - dt1).TotalMilliseconds);
        }

下面是运行结果:

这里我们可以看出并行循环在执行效率上的优势了。

结论1:在对一个数组内的每一个项做单独处理时,完全可以选择并行循环的方式来提升执行效率。

原理1:并行计算的线程开启是缓步开启的,线程数量1,2,4,8缓步提升。(不详,PLinq最多64个线程,可能这也是64)

二、 并行循环的中断和跳出

当在进行循环时,偶尔会需要中断循环或跳出循环。下面是两种跳出循环的方法Stop和Break,LoopState是循环状态的参数。

        /// <summary>
        /// 中断Stop
        /// </summary>
        private void Demo5()
        {
            List<int> data = Program.Data;
            Parallel.For(0, data.Count, (i, LoopState) =>
            {
                if (data[i] > 5)
                    LoopState.Stop();
                Thread.Sleep(500);
                Console.WriteLine(data[i]);
            });
            Console.WriteLine("Stop执行结束。");
        }
        /// <summary>
        /// 中断Break
        /// </summary>
        private void Demo6()
        {
            List<int> data = Program.Data;
            Parallel.ForEach(data, (i, LoopState) =>
            {
                if (i > 5)
                    LoopState.Break();
                Thread.Sleep(500);
                Console.WriteLine(i);
            });
            Console.WriteLine("Break执行结束。");
        }

执行结果如下:

结论2:使用Stop会立即停止循环,使用Break会执行完毕所有符合条件的项。

三、并行循环中为数组/集合添加项

上面的应用场景其实并不是非常多见,毕竟只是为了遍历一个数组内的资源,我们更多的时候是为了遍历资源,找到我们所需要的。那么请继续看。

下面是我们一般会想到的写法:

        private void Demo7()
        {
            List<int> data = new List<int>();
            Parallel.For(0, Program.Data.Count, (i) =>
            {
                if (Program.Data[i] % 2 == 0)
                    data.Add(Program.Data[i]);
            });
            Console.WriteLine("执行完成For.");
        }
        private void Demo8()
        {
            List<int> data = new List<int>();
            Parallel.ForEach(Program.Data, (i) =>
            {
                if (Program.Data[i] % 2 == 0)
                    data.Add(Program.Data[i]);
            });
            Console.WriteLine("执行完成ForEach.");
        }

看起来应该是没有问题的,但是我们多次运行后会发现,偶尔会出现错误如下:

这是因为List是非线程安全的类,我们需要使用System.Collections.Concurrent命名空间下的类型来用于并行循环体内。

说明
BlockingCollection<T> 为实现 IProducerConsumerCollection<T> 的线程安全集合提供阻止和限制功能。
ConcurrentBag<T> 表示对象的线程安全的无序集合。
ConcurrentDictionary<TKey, TValue> 表示可由多个线程同时访问的键值对的线程安全集合。
ConcurrentQueue<T> 表示线程安全的先进先出 (FIFO) 集合。
ConcurrentStack<T> 表示线程安全的后进先出 (LIFO) 集合。
OrderablePartitioner<TSource> 表示将一个可排序数据源拆分成多个分区的特定方式。
Partitioner 提供针对数组、列表和可枚举项的常见分区策略。
Partitioner<TSource> 表示将一个数据源拆分成多个分区的特定方式。

那么我们上面的代码可以修改为,加了了ConcurrentQueue和ConcurrentStack的最基本的操作。

        /// <summary>
        /// 并行循环操作集合类,集合内只取5个对象
        /// </summary>
        private void Demo7()
        {
            ConcurrentQueue<int> data = new ConcurrentQueue<int>();
            Parallel.For(0, Program.Data.Count, (i) =>
            {
                if (Program.Data[i] % 2 == 0)
                    data.Enqueue(Program.Data[i]);//将对象加入到队列末尾
            });
            int R;
            while (data.TryDequeue(out R))//返回队列中开始处的对象
            {
                Console.WriteLine(R);
            }
            Console.WriteLine("执行完成For.");
        }
        /// <summary>
        /// 并行循环操作集合类
        /// </summary>
        private void Demo8()
        {
            ConcurrentStack<int> data = new ConcurrentStack<int>();
            Parallel.ForEach(Program.Data, (i) =>
            {
                if (Program.Data[i] % 2 == 0)
                    data.Push(Program.Data[i]);//将对象压入栈中
            });
            int R;
            while (data.TryPop(out R))//弹出栈顶对象
            {
                Console.WriteLine(R);
            }
            Console.WriteLine("执行完成ForEach.");
        }

ok,这里返回一个序列的问题也解决了。

结论3:在并行循环内重复操作的对象,必须要是thread-safe(线程安全)的。集合类的线程安全对象全部在System.Collections.Concurrent命名空间下。

四、返回集合运算结果/含有局部变量的并行循环

使用循环的时候经常也会用到迭代,那么在并行循环中叫做 含有局部变量的循环 。下面的代码中详细的解释,这里就不啰嗦了。

        /// <summary>
        /// 具有线程局部变量的For循环
        /// </summary>
        private void Demo9()
        {
            List<int> data = Program.Data;
            long total = 0;
            //这里定义返回值为long类型方便下面各个参数的解释
            Parallel.For<long>(0,           // For循环的起点
                data.Count,                 // For循环的终点
                () => 0,                    // 初始化局部变量的方法(long),既为下面的subtotal的初值
                (i, LoopState, subtotal) => // 为每个迭代调用一次的委托,i是当前索引,LoopState是循环状态,subtotal为局部变量名
                {
                    subtotal += data[i];    // 修改局部变量
                    return subtotal;        // 传递参数给下一个迭代
                },
                (finalResult) => Interlocked.Add(ref total, finalResult) //对每个线程结果执行的最后操作,这里是将所有的结果相加
                );
            Console.WriteLine(total);
        }
        /// <summary>
        /// 具有线程局部变量的ForEach循环
        /// </summary>
        private void Demo10()
        {
            List<int> data = Program.Data;
            long total = 0;
            Parallel.ForEach<int, long>(data, // 要循环的集合对象
                () => 0,                      // 初始化局部变量的方法(long),既为下面的subtotal的初值
                (i, LoopState, subtotal) =>   // 为每个迭代调用一次的委托,i是当前元素,LoopState是循环状态,subtotal为局部变量名
                {
                    subtotal += i;            // 修改局部变量
                    return subtotal;          // 传递参数给下一个迭代
                },
                (finalResult) => Interlocked.Add(ref total, finalResult) //对每个线程结果执行的最后操作,这里是将所有的结果相加
                );
            Console.WriteLine(total);
        }

结论4:并行循环中的迭代,确实很伤人。代码太难理解了。

五、PLinq(Linq的并行计算)

上面介绍完了For和ForEach的并行计算盛宴,微软也没忘记在Linq中加入并行计算。下面介绍Linq中的并行计算。

4.0中在System.Linq命名空间下加入了下面几个新的类:

说明
ParallelEnumerable 提供一组用于查询实现 ParallelQuery{TSource} 的对象的方法。这是 Enumerable 的并行等效项。
ParallelQuery 表示并行序列。
ParallelQuery<TSource> 表示并行序列。

原理2:PLinq最多会开启64个线程

原理3:PLinq会自己判断是否可以进行并行计算,如果不行则会以顺序模式运行。

原理4:PLinq会在昂贵的并行算法或成本较低的顺序算法之间进行选择,默认情况下它选择顺序算法。

在ParallelEnumerable中提供的并行化的方法

ParallelEnumerable 运算符 说明
AsParallel() PLINQ 的入口点。指定如果可能,应并行化查询的其余部分。
AsSequential() 指定查询的其余部分应像非并行 LINQ 查询一样按顺序运行。
AsOrdered() 指定 PLINQ 应保留查询的其余部分的源序列排序,直到例如通过使用 orderby 子句更改排序为止。
AsUnordered() 指定查询的其余部分的 PLINQ 不需要保留源序列的排序。
WithCancellation() 指定 PLINQ 应定期监视请求取消时提供的取消标记和取消执行的状态。
WithDegreeOfParallelism() 指定 PLINQ 应当用来并行化查询的处理器的最大数目。
WithMergeOptions() 提供有关 PLINQ 应当如何(如果可能)将并行结果合并回到使用线程上的一个序列的提示。
WithExecutionMode() 指定 PLINQ 应当如何并行化查询(即使默认行为是按顺序运行查询)。
ForAll() 多线程枚举方法,与循环访问查询结果不同,它允许在不首先合并回到使用者线程的情况下并行处理结果。
Aggregate() 重载 对于 PLINQ 唯一的重载,它启用对线程本地分区的中间聚合以及一个用于合并所有分区结果的最终聚合函数。

下面是PLinq的简单代码

        /// <summary>
        /// PLinq简介
        /// </summary>
        private void Demo11()
        {
            var source = Enumerable.Range(1, 10000);
            //查询结果按source中的顺序排序
            var evenNums = from num in source.AsParallel().AsOrdered()
                       where num % 2 == 0
                       select num;
            //ForAll的使用
            ConcurrentBag<int> concurrentBag = new ConcurrentBag<int>();
            var query = from num in source.AsParallel()
                        where num % 10 == 0
                        select num;
            query.ForAll((e) => concurrentBag.Add(e * e));
        }

上面代码中使用了ForAll,ForAll和foreach的区别如下:

以上就是详解c# 并行计算的详细内容,更多关于c# 并行计算的资料请关注我们其它相关文章!

(0)

相关推荐

  • C#中HttpWebRequest的用法详解

    本文实例讲述了C#中HttpWebRequest的用法.分享给大家供大家参考.具体如下: HttpWebRequest类主要利用HTTP 协议和服务器交互,通常是通过 GET 和 POST 两种方式来对数据进行获取和提交.下面对这两种方式进行一下说明: GET 方式: GET 方式通过在网络地址附加参数来完成数据的提交,比如在地址 http://www.jb51.net/?hl=zh-CN 中,前面部分 http://www.jb51.net表示数据提交的网址,后面部分 hl=zh-CN 表示附

  • c#使用多线程的几种方式示例详解

    (1)不需要传递参数,也不需要返回参数 ThreadStart是一个委托,这个委托的定义为void ThreadStart(),没有参数与返回值. 复制代码 代码如下: class Program { static void Main(string[] args) { for (int i = 0; i < 30; i++) { ThreadStart threadStart = new ThreadStart(Calculate); Thread thread = new Thread(thr

  • c#并行任务多种优化方案分享(异步委托)

    遇到一个多线程任务优化的问题,现在解决了,分享如下. 假设有四个任务: 任务1:登陆验证(CheckUser) 任务2:验证成功后从Web服务获取数据(GetDataFromWeb) 任务3:验证成功后从数据库获取数据(GetDatFromDb) 任务4:使用2.3的数据执行一个方法 (StartProcess) 一个比较笨的方法(本人最开始的方法,记为方法1)是直接开启一个线程,按照顺序依次执行四个任务: 复制代码 代码如下: new Thread(delegate              

  • 深入C#中get与set的详解

    释一:属性的访问器包含与获取(读取或计算)或设置(写)属性有关的可执行语句.访问器声明可以包含 get 访问器或 set 访问器,或者两者均包含.声明采用下列形式之一:get {}set {}get 访问器get 访问器体与方法体相似.它必须返回属性类型的值.执行 get 访问器相当于读取字段的值.以下是返回私有字段 name 的值的 get 访问器: 复制代码 代码如下: private string name;   // the name fieldpublic string Name  

  • asp.net(c#)网页跳转七种方法小结

    ①response.redirect 这个跳转页面的方法跳转的速度不快,因为它要走2个来回(2次postback),但他可以跳 转到任何页面,没有站点页面限制(即可以由雅虎跳到新浪),同时不能跳过登录保护.但速度慢是其最大缺陷!redirect跳转机制:首先是发送一个http请求到客户端,通知需要跳转到新页面,然后客户端在发送跳转请求到服务器端.需要注意的是跳转后内部空间保存的所有数据信息将会丢失,所以需要用到session. 实例 Example that uses Redirect [C#;

  • C#几种截取字符串的方法小结

    1.根据单个分隔字符用split截取 例如 复制代码 代码如下: string st="GT123_1"; string[] sArray=st.split("_"); 即可得到sArray[0]="GT123",sArray[1]="1"; 2.利用多个字符来分隔字符串 例如 复制代码 代码如下: string str = "GTAZB_JiangjBen_123";string[] sArray = s

  • C# 正则表达式 使用介绍

    为了避免以后这样的情况,在此记录下正则表达式的一些基本使用方法附带小的实例.让以后在使用时能一目了然知道他的使用,为开发节约时间,同时也分享给大家 正则元字符 在说正则表达式之前我们先来看看通配符,我想通配符大家都用过.通配符主要有星号(*)和问号(?),用来模糊搜索文件.winodws中我们常会使用搜索来查找一些文件.如:*.jpg,XXX.docx的方式,来快速查找文件.其实正则表达式和我们通配符很相似也是通过特定的字符匹配我们所要查询的内容信息.已下代码都是区分大小写. 常用元字符 代码

  • c#处理3种json数据的实例

    网络中数据传输经常是xml或者json,现在做的一个项目之前调其他系统接口都是返回的xml格式,刚刚遇到一个返回json格式数据的接口,通过例子由易到难总结一下处理过程,希望能帮到和我一样开始不会的朋友. 一.C#处理简单json数据 json数据: 复制代码 代码如下: {"result":"0","res_info":"ok","queryorder_info":"info"} 我这

  • C# 一个WCF简单实例

    WCF实例(带步骤) 复制代码 代码如下: <xmlnamespace prefix ="o" ns ="urn:schemas-microsoft-com:office:office" /> 本篇转自百度文档,自己试过,确实可以用. 以订票为例简单应用wcf 新建一个wcf服务应用程序 在IService1.cs定义服务契约 复制代码 代码如下: namespace WcfDemo { // 注意: 如果更改此处的接口名称 "IService

  • C#中使用split分割字符串的几种方法小结

    第一种方法: 复制代码 代码如下: string s=abcdeabcdeabcde;string[] sArray=s.Split(c) ;foreach(string i in sArray)Console.WriteLine(i.ToString()); 输出下面的结果:abdeabdeabde 第二种方法: 我们看到了结果是以一个指定的字符进行的分割.使用另一种构造方法对多个字符进行分割: 复制代码 代码如下: string s=abcdeabcdeabcdestring[] sArra

  • c#并行编程示例分享

    ParallelTest.cs 复制代码 代码如下: using System;using System.Collections.Generic;using System.Linq;using System.Threading;using System.Threading.Tasks; namespace ParallelTest{    class ParallelTest    {        private static int Timed_Message(String arg_Mess

  • C#连接MySql数据库的方法

    1.要连接MySql数据库必须首先下载MySql官方的连接.net的文件,文件下载地址为http://dev.mysql.com/downloads/connector/net/6.6.html#downloads ,下载平台选择.Net&Mono,下载ZIP免安装版.2.解压缩刚才下载的mysql-connector-net-6.6.6-noinstall.zip文件,里面有几个版本选择,在这里我选V4, 选中这几个文件,然后添加到C#项目的引用中,然后就可以编写程序进行数据库的操作了. 3.

随机推荐