【问题标题】:Recycling threads to increase parallelism in recursive calls in C#回收线程以增加 C# 中递归调用的并行性
【发布时间】:2015-04-07 01:01:02
【问题描述】:

我在 C# 中有一个任务,需要递归计算树状结构中项目的属性。基本上,我只是进行递归深度优先搜索,但使用线程来并行化该过程。这意味着,从树的根开始,我启动N 线程,每个根的子项一个(最多可用线程的最大值,我们将说是L)。因此,如果N 严格大于L,我将有(N - L) 根子级等待处理,这将最终逐渐发生,因为函数一直向上返回,线程终止并被回收到处理下一个根子元素(如果有任何未处理的)。

我只是考虑一个或多个原始线程的运行时间相对较长的情况,足以让其他根子(及其后代)有时间在这个或那些线程有时间之前处理去完成。这意味着我将有潜在的可用线程在不做任何工作的情况下放置。

我想要完成的是实际使用这些线程来加速更长运行的执行路径。我在想Open MPI,实际上你总是可以让所有可用线程动态地处理剩余的任务。这意味着,在每次调用递归方法之前,我应该以某种方式检查是否所有根子节点都已被处理(因为它们是优先级),如果是这样,则生成递归调用的线程版本,然后移动继续下一个。

我试图在下面的(画得很差的)图表中说明这种行为(其中每个横向分支都是递归方法调用/返回)。第一张图片显示了thread2 完成的速度比thread1 快得多的情况,然后只是闲置而没有任何进一步的贡献。第二张图片显示了所需的行为,在进行下一个递归调用之前,我们实际上使用可用线程对其进行线程化,然后继续处理下一个子进程。第 n 级部分只是意味着它可以在递归中走得更深。

您知道是否有任何方法可以在 C# 中促进此过程?就代码的并行化(在构造、数据结构等方面)而言,最好的策略是什么?另外,是否有一种简单的方法可以“减少”(聚合)计算结果?

【问题讨论】:

    标签: c# multithreading recursion


    【解决方案1】:

    只需使用线程池,而不是创建自己的线程。让每个节点由单个线程池任务处理。然后,线程池将处理创建/拆除线程,这样它就有足够的线程来处理分配给它的操作的平均吞吐量,而不会让线程长时间空闲。

    【讨论】:

      【解决方案2】:

      我认为您应该考虑将TPL library 用于这种方法,可能是您自己实现的TaskScheduler 类。我认为这将适合您,因为:

      1. Tpl 库内部使用ThreadPool,因此您无需担心系统资源管理。默认任务调度程序基于 .NET Framework 4 ThreadPool,它提供用于负载平衡的工作窃取。

      2. 您可以attach a child tasks to the parent task,这将涵盖您算法的递归部分,如下所示:

        using System;
        using System.Threading;
        using System.Threading.Tasks;
        
        public class Example
        {
            public static void Main()
            {
                var parent = Task.Factory.StartNew(() => {
                    Console.WriteLine("Parent task executing.");
                    var child = Task.Factory.StartNew(() => {
                          Console.WriteLine("Attached child starting.");
                          Thread.SpinWait(5000000);
                          Console.WriteLine("Attached child completing.");
                    }, TaskCreationOptions.AttachedToParent);
                });
                parent.Wait();
                Console.WriteLine("Parent has completed.");
           }
        }
        // The example displays the following output: 
        //       Parent task executing. 
        //       Attached child starting. 
        //       Attached child completing. 
        //       Parent has completed.
        
      3. Tasks can be canceled 通过CancellationToken 这样您就可以轻松地通过drop the full child collection 回到第一级任务。您也可以实现自己的TaskScheduler 并使用它来确定接下来应该运行哪个任务。有关如何创建和使用自定义任务计划程序的更多信息,请参阅How to: Create a Task Scheduler That Limits Concurrency。有关自定义调度程序的其他示例,请参阅 MSDN 代码库网站上的 Parallel Extensions Samples。

      4. TPL 有几个新的公共类型,它们在并行和顺序方案中都很有用。其中包括System.Collections.Concurrent 命名空间中的几个线程安全、快速和可扩展的集合类,以及几个新的同步类型,例如System.Threading.Semaphore 和System.Threading.ManualResetEventSlim,对于特定类型的工作负载,它们比它们的前辈更有效。 .NET Framework 4 中的其他新类型(例如 System.Threading.Barrier 和 System.Threading.SpinLock)提供了早期版本中不可用的功能。如需更多信息,请参阅Data Structures for Parallel Programming。
      5. 您可以将最新的 C# 功能 async/await 用于此算法,这样您的代码不仅可以并行执行,还可以异步执行。
      6. 无法确定您对 aggregate 的含义,但我认为 TPL Dataflow 命名空间可能对您有所帮助。

      【讨论】:

        猜你喜欢
        • 2020-05-04
        • 2019-02-05
        • 2021-06-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多