2010-08-15 49 views
14

特别是,我正在考虑使用TPL来启动(并等待)外部进程。在决定开始另一项任务之前,TPL是否会考虑总的机器负载(包括CPU和I/O)(因此 - 在我的情况下 - 是另一个外部过程)?任务并行库(或PLINQ)是否考虑其他进程?

例如:

我已经得到了需要进行编码或转码(例如,从WAV后手或FLAC到MP3)约100媒体文件。编码是通过启动外部进程(例如FLAC.EXE或LAME.EXE)完成的。每个文件大约需要30秒。每个进程大都是CPU限制的,但是其中有一些I/O。我有4个内核,所以最坏的情况(通过将解码器编码到编码器中进行代码转换)仍然只使用2个内核。我想这样做:

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

这是否会揭开序幕100个任务(因此200的外部进程的CPU竞争)?还是会看到CPU很忙,一次只能做2-3个?

+0

当然,我应该做的是将一个TaskCompletionSource连接到Process.Exited事件,然后有一个返回Task的TranscodeAsync方法。它会是非阻塞的。然后,我可以对任务进行更细致的控制,同时仍然呆在第三方物流的粮食中。 – 2012-02-03 14:19:17

回答

21

你会在这里遇到几个问题。调度程序的饥饿回避机制会将您的任务视为等待进程时被阻止。它会发现很难区分死锁线程和简单等待进程完成的线程。因此,如果您的任务运行或很长时间(见下文),它可能会安排新的任务。登山启发式应考虑系统的整体负载,无论是从应用程序还是其他应用程序。它只是试图最大限度地完成工作,所以它会增加更多的工作,直到系统的整体吞吐量停止增加,然后退出。我不认为这将影响您的应用程序,但避免问题的可能会。

你可以找到更多的细节,如何这在Parallel Programming with Microsoft®.NET,科林坎贝尔,拉尔夫·约翰逊,米勒阿德,斯蒂芬Toub(是online早期草案)所有的作品。

“的.NET线程池自动根据管理工作者在游泳池 线程数它添加和删除线程内置 启发式的.NET线程池具有注入 线程两种主要机制:A饥饿避免机制,增加了工人 线程,如果它看到在排队的物品,并试图在使用作为 几个线程尽可能地最大化吞吐量爬山 启发而毫无进展。

饥饿避税的目的是为了防止死锁这种类型的死锁可能发生在工作人员th读取等待同步 事件,该事件只能由线程池的全局或本地队列中仍处于待执行 的工作项目满足。如果有一个固定的 工作者线程数,并且所有这些线程同样被阻止,则系统将无法取得进一步的进展。 添加新的工作线程可解决问题。

爬山启发式算法的一个目标是在线程被I/O或其他等待条件阻塞的等待条件 阻止处理器时提高内核的利用率 。默认情况下,托管线程池每个核心有一个 工作线程。如果其中一个工作线程变为 被阻止,则核心可能未充分利用,这取决于计算机整体工作负载上的 。线程注入逻辑 不区分被阻塞的线程和正在执行冗长的处理器密集型操作的线程 。因此,每当线程池的全局或本地队列包含待定的 工作项时,需要花费很长时间运行的活动工作项(大于 半秒)才会触发新线程池工作线程的创建 线程。

.NET线程池有机会每工作项完成 时间或以500毫秒间隔注入线程,无论哪个 更短。线程池使用此机会尝试添加线程 (或将它们带走),并根据线程数的以前更改的反馈进行指导。如果添加线程似乎有助于吞吐量,则线程池会增加更多;否则,它会减少工作线程的数量。这种技术被称为爬山启发式。 因此,保持单个任务简短的一个原因是为了避免 “饥饿检测”,但是另一个保持简短的原因是 给线程池更多的机会来提高吞吐量,调整线程数量为 。单个任务的持续时间越短,线程池可以测量吞吐量的频率越高,并且相应地调整线程计数。

为了使这个具体,请考虑一个极端的例子。假设你有一个复杂的财务模拟和 操作,其中每个操作需要花费10分钟的时间才能完成,需要500个处理器密集的 操作。如果您在全局队列中为这些操作的每个 创建顶级任务,则会发现大约五分钟后,线程池将增长到500个工作线程。原因在于 线程池将所有任务视为被阻止,并开始以每秒大约两个线程的速率添加新的线程。

500工作线程有什么问题?原则上,没有任何内容,如果你有500个内核供他们使用,并且海量内存系统为 。事实上,这是并行计算的长期愿景。 但是,如果您的计算机上没有多个内核,则在多个线程正在竞争时间片的情况下,您的系统为 。这种情况被称为处理器超额订购。允许许多处理器密集型线程在单个内核上竞争时间会增加上下文切换开销,这会严重降低整个系统的吞吐量。即使你没有耗尽内存,在这个 的情况下的性能可能会比连续计算更糟糕得多。 (每个上下文切换需要6,000到8,000个处理器周期。) 上下文切换的开销不是唯一的开销来源。 .NET中的托管线程占用大约1兆字节的堆栈空间,无论该空间是否用于当前正在执行的功能。 需要大约200,000个CPU周期来创建一个新线程,并且大约需要100,000个周期来退出一个线程。这些是昂贵的操作。

只要你的任务不需要每个分钟,线程池的登山算法就会最终意识到它有太多线程 并自行减少。但是,如果你的任务 占用一个工作线程几秒或几分钟或几小时,那么 将抛出线程池的启发式,此时 应考虑替代方案。

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

作为最后的手段,您可以使用SetMaxThreads方法 配置ThreadPool类有上限的工作线程的数量 ,通常等于(这是 Environment.ProcessorCount属性)核心数量。此上限适用于 的整个过程,包括所有的应用程序域“

+0

+1'Parallel.ForEach'的重载也可以帮助将'ParallelOptions'对象与'MaxDegreeOfParallelism'一起使用。 – 2010-09-02 02:14:02

+0

+1我找不到该书的在线版本 – Paparazzi 2012-06-21 13:58:07

+0

这本书就在这里。 http://msdn.microsoft.com/en-us/library/ff963553.aspx – 2012-07-04 05:24:54

2

简短的回答是:不

内部,TPL使用标准ThreadPool安排其工作,这样你实际上是在问是否。 ThreadPool考虑了机器负载,但它并没有。限制同时运行的任务数量的唯一的事情是线程池中的线程数,没有别的。

是否有可能让外部进程报告回到你的应用程序,一旦他们准备好了?在这种情况下,你不必等待它们(保持线程占用)。

-1

使用TPL/ThreadPool安排测试大量循环旋转任务的测试。使用外部应用程序,我使用proc亲和力将其中一个内核加载到100%。活动任务的数量从未减少。

更好的是,我运行了同一CPU密集型.NET TPL应用程序的多个实例。所有应用程序的线程数都是相同的,即使我的机器几乎不可用,但永远不会低于内核数量。所以理论上,TPL使用可用内核的数量,但从不检查其实际负载。我认为这是一个非常糟糕的实施。

+0

我认为TPL在_minimum_分配的线程数等于核心数。它确实检查负载,并可能增加线程数量,但不会减少低于最小值的线程数量。 – 2016-10-05 21:02:23

+0

这是正确的。这是最低限度的默认值。这是一个警察。您是否期望我们的开发人员创建可监控整体CPU负载的代码(不包括我们正在运行的应用程序),并相应地减少线程池消耗的最小和最大线程数?或者我们应该手动将最小值从开始设置为1?或者也许2?或CPU计数/ 2? 而btw不,它不监视实际的CPU负载。它只监视自己的线程。就这样。 – MoonStom 2016-10-05 21:40:06

+0

发生在我身上的事情是,TPL很贪婪,它可能会爬到CPU超额订阅的位置,如果这有利于任务的执行速度,所以你是正确的,TPL监视任务/秒而不是CPU负载,一些缺点。我的观点是,我不认为微软希望开发人员在使用TPL时改变处理器亲和力。 – 2016-10-05 22:24:05

相关问题