【C#】并行编程实战:实现数据并行(1)

本教程对应学习工程:魔术师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        

猜你喜欢

转载自blog.csdn.net/cyf649669121/article/details/131424639