c# - 每个孩子运行一个单独的任务
问题描述
我有一个树数据结构,每个兄弟姐妹都可以彼此并行处理。
目前我创建了一个单独的任务来处理每个孩子。这是幼稚的吗?一旦树具有给定的大小/深度,它会损害性能吗?
或者 CLR 是否设计为处理任意负载的任务,因为它们没有绑定到特定的操作系统线程?!
解决方案
当问一个问题时,“为什么”可能与什么和如何一样重要。从(丢失的)评论中:
好吧,这是一个 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 数据流库支持。您可以将作业拆分为单独的块,每个块都在自己的任务上运行。一个块可以加载和解析文件,第二个块可以从外部服务请求一些数据,最后一个块可以处理结果。
假设文件只包含一些股票数据,您必须从外部服务请求更多。第一个块可以解析文件并提取股票,第二个可以请求附加数据,最后一步可以处理所有数据。创建数据流将允许所有作业并行运行。网络绑定下载可能发生在 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);
推荐阅读
- ruby-on-rails - 如何在 rails6 应用程序中重新生成测试目录?
- sequelize.js - Sequelize 引用错误列名的多对多关系
- java - 这里的代码中实际发生了什么
- android - 使用 Firebase 对 Android Studio 进行 OTP 验证
- c++ - curl_easy_perform() 失败:SSL 对等证书或 SSH 远程密钥不正确
- c# - 为什么有时我需要添加 Nuget 依赖项,即使它们没有被我的项目直接使用?
- google-chrome - 如何将远程脚本添加到 chrome 扩展内容安全策略?
- php - 不能将命名空间与自动加载器一起使用
- c++ - 从 exprTk 获取向量作为输出
- javascript - 路由。你如何转移财产?