特别是,我正在考虑使用TPL开始(并等待)外部进程。 TPL在决定启动另一个任务之前(因此 - 在我的情况下 - 另一个外部进程)之前,是否查看总计负载(CPU和I/O)?

例如:

我有大约100个需要编码或转码的媒体文件(例如从WAV到FLAC或FLAC到MP3)。编码是通过启动外部进程(例如flac.exe或lame.exe)完成的。每个文件大约需要30秒。每个过程主要是cpu绑定的,但是其中有一些I/O。我有4个核心,因此最坏的情况(通过将解码器输送到编码器中进行转编码)仍然只使用2个内核。我想做类似的事情:

Parallel.ForEach(sourceFiles,
    sourceFile =>
        TranscodeUsingPipedExternalProcesses(sourceFile));

这会启动100个任务(因此有200个外部流程争夺CPU)?还是会看到CPU很忙,一次只能2-3?

有帮助吗?

解决方案

您将在这里遇到一些问题。调度程序的饥饿避免机制将使您的任务在等待过程时被阻止。它将发现很难区分僵局,而只是等待一个过程完成。结果,如果您的任务运行或长时间(请参见下文),它可能会安排新任务。丘陵启发式措施应考虑到系统的总体负载,包括您的应用程序和其他应用程序。它只是试图最大程度地提高完成的工作,因此它将增加更多的工作,直到系统的总体吞吐量停止增加,然后退缩。我不 思考 这将影响您的应用程序,但是避免造成的问题可能会。

您可以找到有关所有这些如何工作的更多详细信息 使用Microsoft®.net并行编程, ,科林·坎贝尔,拉尔夫·约翰逊,阿德·米勒,斯蒂芬·图布(较早的草稿是 在线的).

“ .NET线程池自动管理池中的工作线数量。它根据内置的启发式添加并删除线程。.NET线程池具有注入线程的两个主要机制:避免饥饿的机制,可以添加Worker机制如果线程在排队的物品和山丘启发式方面没有进展,该线程未取得进展,该启发式启发式试图在使用尽可能少的线程的同时最大程度地提高吞吐量。

避免饥饿的目标是防止死锁。当工作线程等待同步事件时,只有在线程池的全局或本地队列中仍在等待的工作项时,就会发生这种僵局。如果有固定数量的工作线程,并且所有这些线程都被类似地阻塞,那么系统将无法进一步进一步进展。添加新的工作线程可以解决问题。

爬山启发式的目的是在线程被I/O或其他等待条件阻塞的情况阻塞时改善核心的利用率。默认情况下,托管线程池每个核心都有一个工作线程。如果这些工人线程中的一个被阻塞,则有可能根据计算机的整体工作负载来实现核心的充分利用。线程注入逻辑不能区分被阻塞的线程和执行冗长的处理器密集型操作的线程。因此,每当线程池的全局或本地队列包含未决的工作项时,活动项目需要很长时间才能运行(超过半秒),都会触发新线程池工作线程的创建。

.NET线程池有机会每次工作项完成或以500毫秒间隔(以较短者为准)注入线程。线程池利用此机会尝试添加线程(或将其带走),并在线程计数先前更改的反馈下进行指导。如果添加线程似乎有助于吞吐量,则线程池会添加更多;否则,它减少了工作线的数量。该技术称为爬山启发式。因此,保持单个任务简短的原因之一是避免“饥饿检测”,但是使它们简短的另一个原因是,通过调整线程计数,使线程池更多的机会来改善吞吐量。单个任务的持续时间越短,线程池越多地测量吞吐量并相应地调整线程计数。

要制造这种具体,请考虑一个极端的例子。假设您进行了500次处理器密集型操作的复杂财务模拟,每项操作平均需要十分钟才能完成。如果为每个操作中的每个操作创建顶级队列中的顶级任务,您会发现大约五分钟后,线程池将增长到500个工作线程。原因是线程池将所有任务视为阻塞的所有任务,并开始以每秒大约两个线程的速率添加新线程。

500个工作线程怎么了?原则上,如果您有500个核心供它们使用和大量的系统内存。实际上,这是平行计算的长期视野。但是,如果您的计算机上没有那么多核心,那么您正处于许多线程争夺时间切片的情况。这种情况被称为处理器过度检查。允许许多处理器密集型线程在单个核心上竞争时间,从而添加了上下文切换开销,可以严重减少整体系统吞吐量。即使您没有用完内存,在这种情况下的性能也可能比顺序计算要差得多。 (每个上下文开关都需要6,000至8,000个处理器周期。)上下文开关的成本并不是唯一的开销来源。 .NET中的托管线程大致消耗了堆栈空间的兆字节,无论该空间是否用于当前执行功能。创建一个新线程,大约需要200,000个CPU周期,而大约有100,000个循环才能退役线程。这些是昂贵的操作。

只要您的任务并非每次都花几分钟,线程池的爬山算法最终就会意识到它具有太多的线程并自行减少。但是,如果您确实有占据工人线程数秒或几分钟或几个小时的任务,则可以抛弃线程池的启发式方法,而那时您应该考虑替代方案。

第一个选项是将您的应用程序分解为较短的任务,这些任务已足够快,可以使线程池成功控制线程数以获得最佳吞吐量。第二种可能性是实现自己的任务调度程序对象,该对象不执行线程注入。如果您的任务持续时间很长,则不需要高度优化的任务调度程序,因为与任务的执行时间相比,调度成本可以忽略不计。 MSDN®开发人员程序具有一个简单的任务调度程序实现的示例,该实现限制了最大并发程度。有关更多信息,请参见本章末尾的“进一步阅读”部分。

作为最后的手段,您可以使用setMaxThreads方法来配置用工作线数量的上限,通常等于核心数量(这是环境。该上限适用于整个过程,包括所有应用程序。”

其他提示

最简洁的答案是不。

在内部,TPL使用标准 ThreadPool 安排其任务。因此,您实际上是在问 ThreadPool 考虑机器负载,但没有考虑。唯一限制同时运行任务数量的是线程池中的线程数,别无其他。

是否可以将外部流程报告后将外部流程报告回到您的应用程序中?在这种情况下,您不必等待它们(保持线程占据)。

使用TPL/ThreadPool进行测试,以安排大量进行循环旋转的任务。使用外部应用程序,我使用PROC亲和力将其中一个内核加载到100%。主动任务的数量永远不会减少。

更好的是,我运行了相同的CPU密集型.NET TPL应用程序的多个实例。所有应用程序的线程数量都是相同的,即使我的机器几乎无法使用,也从未超过核心数量。

因此,除了理论外,TPL使用可用的核心数量,但从未检查其实际负载。在我看来,实施非常差。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top