本教程对应学习工程:魔术师Dix / HandsOnParallelProgramming · GitCode
到目前为止,我们已经掌握了并行编程、任务和任务并行的基础知识。本章将讨论并行编程的另一个重要方面,即数据并行。
任务并行可以为每个参与线程创建一个单独的工作单元,而数据并行则可以创建一个公共任务,由源集合中每个参与的线程执行。由于源集合已经分区,因此可以由多个线程同时对其进行处理。理解数据并行对于从循环/集合中获得最佳性能非常重要。
之前是把整个一章全部贴完,导致内容过大,动不动就几千字。后面还是多分篇幅,减少单篇长度。
1、从顺序循环到并行循环
TPL 通过 System.Threading.Tasks.Parallel 类支持数据并行,该类提供 For 和 Foreach 循环的并行实现。作为开发人员无需同步或创建任务,而是由并行类处理的。
首先我们做一个如下的列表:
public static List<int> GetTestList(int length)
{
List<int> list = new List<int>();
//简单地将数据顺序添加,以进行测试
for (int i = 0; i < length; i++)
list.Add(i);
return list;
}
让我们首先顺序执行任务:
private void RunBySequence()
{
var L = TestFunction.GetTestList(10);
foreach (var item in L)
{
Debug.Log(item);
}
}
这个结果就很显然了:
数据同样也是被顺序地打印出来。
接下来我们开始数据并行:
1.1、Parallel.Invoke
这是并行执行一组操作的最基本方式,并且也是并行 for 和 foreach 循环的基础形式。使用时要注意以下要点:
-
不能保证并行:操作是并行还是顺序将取决于 TaskScheduler 。
-
Parallel.Invoke 不保证传递操作的执行顺序。
-
他将阻塞线程,直到所有操作完成。
测试调用代码如下:
private void RunByParallelInvoe()
{
var L = TestFunction.GetTestList(10);
foreach (var item in L)
{
Parallel.Invoke(() =>
{
Debug.Log(item);
});
}
}
上代码我试了几次,调用结果都是顺序执行的,打印结果和之前一模一样。我怀疑是我的测试环境下,这个任务比较简单,没有给系统足够压力以并行。
1.2、Parallel.For
Parallel.For 是 For 循环的一种变体,不同之处在于其迭代是并行运行的。
private void RunByParallelFor()
{
int length = commonPanel.GetInt32Parameter();
var L = TestFunction.GetTestList(length);
Parallel.For(0, length, (i, state) =>
{
Debug.Log(L[i]);
});
}
这次打印的结果就有显然区别了:
可以看到打印顺序并不是顺序的,而是并行的。在并行方法中,传入的两个参数,一个是当前下标,另一个是 ParallelLoopState ,可以调用其 Stop 和 Break 方法来中断线程运行:
var result = Parallel.For(0, length, (i, state) =>
{
Debug.Log(L[i]);
state.Break();
});
Debug.Log($"ParallelFor Result : {result.IsCompleted} | {result.LowestBreakIteration}");
这里我们传入100个值的数组,打印出来结果如下:
可见只打印了34次,而不是全部完成的100次。即便一开始运行就 Break 了,仍然会有 34 个任务执行了。把 Break 换成 Stop 执行效果是一样的,应该区别不大。
对于返回的 ParallelLoopResult 其值有2个,意义如下:
IsCompleted | LowestBreakIteration | 表示意义 |
true | N/A | 运行至完成 |
false | Null | 循环停止了预匹配 |
false | 非零整数值 | 在循环中调用了 Break |
对于某些集合来说,顺序执行的工作速度更快,具体取决于循环的语法和正在执行的工作类型。
1.3、Parallel.Foreach
Parallel.ForEach 循环是 ForEach 循环的一种变体,区别在于其中的迭代可以按并行方式运行。Parallel.ForEach 将对源集合进行分区,然后调度工作以运行多个线程。
private void RunByParallelForeach()
{
var L = TestFunction.GetTestList(10);
var result = Parallel.ForEach(L, (i, state) =>
{
Debug.Log(L[i]);
});
Debug.Log($"Parallel.ForEach Result : {result.IsCompleted} | {result.LowestBreakIteration}");
}
代码上修改很简单,输出结果也相同:
(未完待续)
本教程对应学习工程:魔术师Dix / HandsOnParallelProgramming · GitCode