【转】【C#】【Thread】【Parallel】并行计算

并行计算

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

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);
        }    

下面是运行结果:

image

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

结论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执行结束。");
        }

 执行结果如下:

image

结论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.");
        }

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

image

这是因为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的区别如下:

image

PLinq的东西很繁杂,但是都只是几个简单的方法,熟悉下方法就好了。

 

原文地址:http://www.cnblogs.com/sorex/archive/2010/09/16/1828214.html

<think>好的,我现在要帮用户了解如何在C#中实现并行计算。用户可能是一个有一定C#基础的开发者,想要提升程序性能,尤其是利用多核处理器的能力。用户提到“并行计算”,所以需要先明确并行和并发的区别,但根据引用内容,用户可能更关注如何具体实现,比如多线程、任务并行库等。 首先,我需要整理C#中实现并行计算的几种主要方法。根据引用中的信息,有Thread类、线程池、Task、Parallel类、PLINQ和async/await。这些内容在引用3、4、5都有提到。需要分别解释每种方法,并给出示例代码,这样用户能更直观地理解。 接下来,用户需要示例,所以每个方法都要配一个简单的代码片段。比如,使用Parallel.For处理数组,这在引用4中有例子,可以引用过来。另外,引用5提到的TPL和PLINQ也是重点,需要详细说明。 还要注意用户可能的需求,比如如何处理文件读取和CPU密集型任务同时进行,这可能涉及任务并行和异步编程的结合。引用1和引用2中的示例可能相关,可以结合起来。 另外,需要考虑用户可能的误区,比如并行并不总是更快,线程安全的问题,比如共享数据的处理。这些在回答中需要提醒,避免用户在实际应用中出现问题。 最后,生成相关问题,帮助用户进一步学习。比如性能优化、异常处理、与多线程的区别等,这些都是用户可能后续会问的问题。 总结步骤:先介绍不同的实现方法,每种方法给出示例代码,并附上注意事项,最后列出相关问题。确保引用正确,比如引用4中的Parallel.For例子,引用5中的TPL和PLINQ,引用3中的Thread类等。</think>### C# 并行计算实现方法及示例 在C#中实现并行计算可通过多种方式完成,以下为常用方法及代码示例: --- #### 1. **使用`Parallel`类(TPL核心组件)** `System.Threading.Tasks.Parallel` 提供简化的并行循环操作,适用于数据并行场景。 ```csharp // 示例1:并行处理数组元素 double[] data = new double[10000]; Parallel.For(0, data.Length, i => { data[i] = Math.Sqrt(i) * Math.Cos(i); // 对每个元素进行并行计算 }); // 示例2:并行处理文件集合 List<string> files = Directory.GetFiles("D:\\Data").ToList(); Parallel.ForEach(files, file => { string content = File.ReadAllText(file); // 处理文件内容 }); ``` **特点**:自动分配线程资源,支持负载均衡[^4][^5]。 --- #### 2. **任务并行(`Task`类)** 通过`Task`实现异步与并行组合操作,适合独立任务拆分。 ```csharp // 示例:同时执行CPU计算与文件读取 Task computeTask = Task.Run(() => { // CPU密集型计算 for (int i = 0; i < 1e8; i++) { /* 模拟计算 */ } }); Task readTask = Task.Run(() => { string text = File.ReadAllText("data.txt"); // 处理文件内容 }); Task.WaitAll(computeTask, readTask); // 等待所有任务完成 ``` **优势**:灵活控制任务生命周期,支持取消和延续操作[^3]。 --- #### 3. **PLINQ(并行LINQ)** 通过`AsParallel()`扩展方法实现查询并行化。 ```csharp // 示例:并行筛选偶数并平方 List<int> numbers = Enumerable.Range(1, 1000000).ToList(); var result = numbers.AsParallel() .Where(n => n % 2 == 0) .Select(n => n * n) .ToList(); ``` **注意**:小数据集可能因线程调度开销导致性能下降。 --- #### 4. **线程池(`ThreadPool`)** 适用于短期、轻量级任务的并行执行。 ```csharp // 示例:使用线程池执行任务 ThreadPool.QueueUserWorkItem(state => { Console.WriteLine("线程池任务1执行"); }); ThreadPool.QueueUserWorkItem(state => { Console.WriteLine("线程池任务2执行"); }); ``` **适用场景**:避免频繁创建/销毁线程的开销[^3]。 --- #### 5. **`async/await`异步模型** 与并行计算结合提升响应性。 ```csharp // 示例:异步并行执行多个HTTP请求 async Task DownloadDataAsync() { HttpClient client = new HttpClient(); Task<string> task1 = client.GetStringAsync("https://api.example.com/data1"); Task<string> task2 = client.GetStringAsync("https://api.example.com/data2"); string[] results = await Task.WhenAll(task1, task2); // 处理结果 } ``` **优势**:非阻塞I/O操作,提升UI响应速度[^3]。 --- ### 关键注意事项 1. **线程安全**:共享资源需使用`lock`、`ConcurrentBag<T>`等线程安全容器。 2. **异常处理**:使用`AggregateException`捕获并行任务中的异常。 3. **性能权衡**:根据任务类型(CPU密集型/I/O密集型)选择策略。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值