繁体   English   中英

意外的 Parallel.ForEach 循环行为

[英]Unexpected Parallel.ForEach loop behavior

您好我正在尝试使用Parallel.ForEach循环来模拟多线程。 下面是我的 function:

public void PollOnServiceStart()
{
    constants = new ConstantsUtil();
    constants.InitializeConfiguration();

    HashSet<string> newFiles = new HashSet<string>();

    //string serviceName = MetadataDbContext.GetServiceName();

    var dequeuedItems = MetadataDbContext
        .UpdateOdfsServiceEntriesForProcessingOnStart();
    var handlers = Producer.GetParserHandlers(dequeuedItems);

    while (handlers.Any())
    {
        Parallel.ForEach(handlers,
            new ParallelOptions { MaxDegreeOfParallelism = 4 },
            handler =>
            {
                Logger.Info($"Started  processing a file remaining in Parallel ForEach");
                handler.Execute();
                Logger.Info($"Enqueing one file for next process");
                dequeuedItems = MetadataDbContext
                    .UpdateOdfsServiceEntriesForProcessingOnPollInterval(1);
                handlers = Producer.GetParserHandlers(dequeuedItems);
            });

        int filesRemovedCount = Producer.RemoveTransferredFiles();
        Logger.Info($"{filesRemovedCount} files removed from {Constants.OUTPUT_FOLDER}");
    }
}

所以要解释发生了什么。 function UpdateOdfsServiceEntriesForProcessingOnStart()获得 4 个文件名(4 个因为并行计数)并将它们添加到名为ParserHandler的线程安全 object 中。 然后将这些对象放入列表var handlers中。

我的想法是遍历这个处理程序列表并调用handler.Execute()

Handler.Execute()将文件从网络位置复制到本地驱动器,解析文件并创建多个 output 文件,然后将所述文件发送到网络位置并更新数据库表。

我在这个 Parallel For Each 循环中期望的是,在Handler.Execute()调用之后, UpdateOdfsServiceEntriesForProcessingOnPollInterval(1) function 将从它读取的 db 表中添加一个新文件名到出队的项目容器中,然后将其作为一个项目传递到重新创建的处理程序列表。 这样,在一个文件执行完毕后,每个并行循环都会有一个新文件代替它。

然而,发生的情况是,虽然我确实添加了一个新文件,但它并没有被下一个可用线程执行。 相反,每个并行都必须完成前 4 个文件的执行,然后它将拾取下一个文件。 意思是,在前 4 个并行运行之后,一次只运行一个文件,从而使并行循环的整个点无效。 在所有 4 个文件完成Execute()调用之前添加的初始文件永远不会执行。

IE:

(Start1,Start2,Start3,Start4)一次。 应该发生的事情类似于 (End2, Start5),然后是 (End3, Start6)。 但是正在发生的事情是(结束 2,结束 3,结束 1,结束 4),然后是 Start5。 结束 5。 开始 6,结束 6。 为什么会这样?

因为我们想在一台机器上部署这个服务应用程序的多个实例,所以让一个巨大的列表在队列中等待是没有好处的。 这是浪费,因为其他应用程序实例无法处理。

我正在写一个应该很长的评论作为答案,尽管这是一个糟糕的答案,因为它没有回答这个问题。

请注意,并行文件系统操作不太可能使它们更快,尤其是在存储是经典硬盘的情况下。 磁盘的磁头不能同时在 N 个位置,如果你告诉它这样做只会浪费它的大部分时间旅行而不是读取或写入。

克服访问文件系统带来的瓶颈的最佳方法是确保磁盘始终有工作要做。 不要停止磁盘的工作以进行计算或从/向数据库获取/保存数据。 要做到这一点,您必须同时运行多个工作流。 一个工作流将完全与磁盘进行 I/O,另一个工作流将与数据库连续对话,第三个工作流将通过一个接一个地进行计算来利用 CPU,等等。这种方法称为任务并行(并行执行异构工作),而不是数据并行性(并行执行同质工作, Parallel.ForEach的专长)。 它也称为流水线,因为为了使所有工作流同时运行,您必须在它们之间放置中间缓冲区,因此您创建了一个数据流从缓冲区到缓冲区的管道。 用于此类操作的另一个术语是生产者-消费者模式,它描述了一个短管道,仅由两个构建块组成,第一个是生产者,第二个是消费者。

目前可用于创建管道的最强大的工具是TPL 数据流库。 它提供了多种可以相互链接的“块”(管道段),可以覆盖大部分场景。 您所做的是实例化将构成管道的块,配置它们,告诉每个人应该做什么,将它们链接在一起,为第一个块提供应该处理的初始原始数据,然后最后await最后一个块的Completion 您可以在此处查看使用 TPL 数据流库的示例。

¹在 .NET 平台中作为内置库提供。 还存在强大的第三方工具,例如 Akka.NET。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM