【问题标题】:Running A Separate Task Per Child每个孩子运行一个单独的任务
【发布时间】:2018-07-24 17:11:20
【问题描述】:

我有一个树形数据结构,每个兄弟姐妹都可以相互并行处理。

目前我创建了一个单独的任务来处理每个孩子。这是幼稚的吗?一旦树具有给定的大小/深度,它会损害性能吗?

或者 CLR 是否被设计为处理任意负载的任务,因为它们没有绑定到特定的操作系统线程?!

【问题讨论】:

    标签: c# task-parallel-library


    【解决方案1】:

    这个问题的答案是肯定的。但是为了完全理解它,你必须更深入地研究任务,看看你的任务是long-running tasks还是short running

    第二个链接,包含您问题的答案,但为您总结一下:

    默认情况下,任务是短期运行的。 含义

    任务将在线程池中创建。如果您的任务要执行会消耗大量时间的工作,这意味着 长时间运行 - 您应该避免在线程池中创建任务,因为它会使您的线程池充满越来越多的任务最终会溢出,我们称之为,线程池。

    长时间运行意味着任务将在线程池之外的单个线程上创建,可以使用TaskCreationOptions Enumeration定义:

    TaskCreationOptions.LongRunning
    

    【讨论】:

    • 感谢您的指点。我认为每个人的工作负载完全受 CPU 限制,没有文件或网络 I/O,但我不想施加这种限制,以防将来需要这种东西。也许我会把它拿出来,以后只在树中节点深度
    • 这里会更好。
    【解决方案2】:

    在提出问题时,“为什么”可能与“什么”和“如何”一样重要。来自(丢失的)cmets:

    嗯,这是一个 AST,所以如果我们有一堆文件,每个文件可能有 100 个子树。每个节点的实际处理并不复杂,但处理量可能在 100 多个并发任务。

    这应该是问题的一部分,以及所需处理的描述。

    询问子任务就像询问要在解析器规则中嵌入什么动作:为什么要在解析器规则中嵌入动作?这只是一个技术。适合与否,取决于你在做什么。在 AST 上工作的访问者可能会更好。或对特定规则做出反应的侦听器。例如,ANTLR 提供了所有三个选项。你选择哪一个取决于工作

    就像解析一样,并行计算中的工作类型也很重要。

    当您有大量数据创建比核心更多的任务时,只会浪费时间。最好创建更少的任务并在它们之间拆分数据。这样,单个任务就可以处理所有分配给它的数据,而无需线程切换和延迟。

    这称为数据并行。 TPL 通过Parallel 类和并行LINQ 支持它。如果您有一个包含需要处理的数据的 IEnumerable,您可以并行处理它们,例如:

    Parallel.ForEach(myCollection,singleItem=>SomePrecessing(singleItem));
    

    Parallel.ForEach 将创建(大致)与核心一样多的任务,对数据进行分区并将每个部分发送到一个核心。

    并行 LINQ 允许您并行执行 LINQ 查询的每个运算符,只需添加一个 .AsParallel() 调用,例如:

    var results = from file in listOfFiles.AsParallel()
                  from stock in ParseFileToGetStocks(file)
                  where stock.Price >100
                  group stock by stock.Category into g
                  select new {category=g.Key,Max=g.Max()....}
    

    解析、过滤、分组、聚合部分将在单独的并行步骤中运行。

    只要您可以从树创建 IEnumerable,例如使用迭代器,您就可以将它与 Parallel.For/ForEach 或 PLINQ 一起使用。

    这只是 一个 选项,可能不适合这个问题。毕竟,其中一部分是读取大量文件,一个 IO 操作。为什么不在单独步骤中读取和处理文件,就像 Powershell 或单个命令的 bash 管道一样?

    这是一个 数据流 问题,由 TPL Dataflow 库支持。您可以将作业拆分为单独的块,每个块都在自己的任务上运行。一个块可以加载和解析文件,第二个块可以从外部服务请求一些数据,最后一个块可以处理结果。

    假设文件仅包含一些股票数据,您必须从外部服务请求更多。第一个块可以解析文件并提取股票,第二个可以请求附加数据,最后一步可以处理所有数据。创建数据流将允许所有作业并行运行。网络绑定下载可能发生在 CPU 密集型处理步骤处理另一个文件的结果的同时,例如:

    var parserBlock=new TranformManyBlock<string,StockData>(file =>{
                    {
                        var stock=Parse(file);
                        foreach(var stock in stocks)
                        {
                            yield return new StockData(...);
                        }
                    });
    var downloader=new TransformBlock<StockData,CompleteData>(stock =>
                   {
                       var extraData=someService.Get(stock.Symbol, stock.Date....);
                       return new CompleteData(stock,extraData);
                   });
    
     var calculateBlock= new ActionBlock<CompleteData>(stock=>
                   {
                       var results=HeavyProcessing(stock);
                       WriteResults(stock,results);
                   });
    
    var linkOptions=new DataflowLinkOptions{PropagateCompletion=true";
    parserBlock.LinkTo(downloader,linkOptions);
    downloader.LinkTo(calculateBlock,linkOptions);
    

    一旦你有了块管道,你就可以开始向它发布数据:

    foreach(var node in tree)
    {
        parserBlock.Post(node.File);
    }
    

    完成后,将第一个块告诉Complete() 并等待直到最后一个块的所有块都完成:

    parserBlock.Complete();
    await calculateBlock.Completion;
    

    由于下载器只会等待服务的响应,您可以指定例如最多 5 个下载将同时运行:

    var parallelOptions=new ExecutionDataflowBlockOptions{MaxDegreeOfParallelism=5};
    
    var downloader=new TransformBlock<>(...,parallelOptions);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多