一、何时使用并行编程
有大量的计算任务,并且这些任务能分割成几个互相独立的任务块。
并行编程可临时提高CPU 利用率,以提高吞吐量,若客户端系统中的CPU经常处于空闲状态,这个方法就非常有用,但通常并不适合服务器系统。大多数服务器本身具有并行处理能力,例如ASP .NET可并行地处理多个请求。某些情况下,在服务器系统中编写并行代码仍然有用(如果你知道并发用户数量会一直是少数)。但通常情况下,在服务器系统上进行并行编程,将降低本身的并行处理能力,并且不会有实际的 好处。
二、并行编程的两种形式
- 数据并行(data parallelism):指有大量的数据需要处理,并且每一块数据的处理过程基本上是彼此独立的
- 任务并行(task parallelim):指需要执行大量任务,并且每个任务的执行过程基本上是彼此独立的
任务并行可以 是动态的,如果一个任务的执行结果会产生额外的任务,这些新增的任务也可以加入任务池。
三、实现数据并行
- 使用 Parallel.ForEach 方法,它类似于 foreach 循环,应尽可能使用这种做法。
Parallel 类也提供 Parallel.For 方法,这类似于 for 循环,当数据处理过程基于一个索引 时,可使用这个方法。
下面是使用 Parallel.ForEach 的代码例子:
void RotateMatrices(IEnumerable<Matrix> matrices, float degrees)
{
Parallel.ForEach(matrices, matrix => matrix.Rotate(degrees));
}
- 使用 PLINQ(Parallel LINQ), 它为 LINQ 查询提供了 AsParallel 扩展。
跟 PLINQ 相比,Parallel 对资源更加友好,Parallel 与系统中的其他进程配合得比较好 , 而 PLINQ 会试图让所有的 CPU 来执行本进程。Parallel 的缺点是它太明显。很多情况下, PLINQ 的代码更加优美。
IEnumerable<bool> PrimalityTest(IEnumerable<int> values)
{
return values.AsParallel().Select(val => IsPrime(val));
}
不管选用哪种方法,在并行处理时有一个非常重要的准则:
每个任务块要尽可能的互相独立。
四、实现任务并行
Parallel 类的 Parallel.Invoke 方法可以执行“分叉 / 联合”(fork/join)方式的任务并行。
调用该方法时,把要并行执行的委托(delegate)作为传入 参数:
void ProcessArray(double[] array)
{
Parallel.Invoke(
() => ProcessPartialArray(array, 0, array.Length / 2),
() => ProcessPartialArray(array, array.Length / 2, array.Length)
);
}
void ProcessPartialArray(double[] array, int begin, int end)
{
// CPU 密集型的操作……
}
现在 Task 这个类也被用于异步编程,但当初它是为了任务并行而引入的。任务并行中使 用的一个 Task 实例表示一些任务。可以使用 Wait 方法等待任务完成,还可以使用 Result 和 Exception 属性来检查任务执行的结果。直接使用 Task 类型的代码比使用 Parallel 类 要复杂,但是,如果在运行前不知道并行任务的结构,就需要使用 Task 类型。如果使 用动态并行机制,在开始处理时,任务块的个数是不确定的,只有继续执行后才能确 定。通常情况下,一个动态任务块要启动它所需的所有子任务,然后等待这些子任务执 行完毕。为实现这个功能,可以使用 Task 类型中的一个特殊标志:TaskCreationOptions. AttachedToParent。