任务并行库(或PLINQ)是否考虑了其他过程?

Rog*_*mbe 14 task-parallel-library

特别是,我正在寻找使用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));
Run Code Online (Sandbox Code Playgroud)

这将启动100个任务(因此200个外部进程竞争CPU)?或者它会看到CPU忙,一次只做2-3次?

Ade*_*ler 21

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

您可以在与Microsoft®.NET,Colin Campbell,Ralph Johnson,Ade Miller,Stephen Toub(早期草案在线)的并行编程中找到更多详细信息.

".NET线程池自动管理池中的工作线程数.它根据内置启发式添加和删除线程..NET线程池有两个主要的注入线程机制:一个添加工作者的饥饿避免机制线程如果它看不到排队项目没有进展,那么试图在使用尽可能少的线程时尝试最大化吞吐量的爬山式启发式算法.

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

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

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

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

500个工作线程出了什么问题?原则上,如果你有500个内核供它们使用,并且有大量的系统内存,那就没什么了.实际上,这是并行计算的长期愿景.但是,如果您的计算机上没有那么多内核,则会出现许多线程争用时间片的情况.这种情况称为处理器超额订阅.允许许多处理器密集型线程在单个核心上争用时间会增加上下文切换开销,从而严重降低整体系统吞吐量.即使你的内存不足,这种情况下的性能也会比顺序计算更糟糕.(每个上下文切换需要6,000到8,000个处理器周期.)上下文切换的成本不是开销的唯一来源.一个托管的线程.NET消耗大约1兆字节的堆栈空间,无论该空间是否用于当前正在执行的功能.创建新线程需要大约200,000个CPU周期,退出线程大约需要100,000个周期.这些都是昂贵的操作.

只要你的任务不是每个都花费几分钟,线程池的爬山算法最终会意识到它有太多的线程并且自行削减.但是,如果您确实拥有占用工作线程数秒或几分钟或几小时的任务,那么将会抛弃线程池的启发式,此时您应该考虑替代方案.

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

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