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

特别是,我正在使用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次?

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

您可以在Parallel Programming with Microsoft®.NET,Colin Campbell,Ralph Johnson,Ade Miller,Stephen Toub(早期草案是online)中找到更多细节.

“.NET线程池自动管理worker的数量
池中的线程它根据内置的方式添加和删除线程
启发式. .NET线程池有两种主要的注入机制
线程:添加工作者的饥饿 – 避免机制
如果没有看到排队的物品和山坡上没有进展的话
试图在使用as时最大化吞吐量的启发式
线程尽可能少

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

攀登启发式的目标是提高利用率
的线程被I / O或其他等待条件阻塞
这会使处理器失速.默认情况下,托管线程池有一个
每个核心的工作线程.如果这些工作线程之一成为
阻止,有一个机会,核心可能未得到充分利用
在计算机的总体工作量上.线程注入逻辑
不区分被阻塞的线程和线程
这是执行漫长的处理器密集型操作.因此,
每当线程池的全局或本地队列包含挂起
工作项目,需要很长时间运行的活动工作项目(超过
半秒)可以触发创建新的线程池工作器
线程.

.NET线程池有机会每次插入线程
时间工作项目完成或以500毫秒的间隔,以较大者为准
较短.线程池使用这个机会尝试添加线程
(或带走他们),由先前变化的反馈指导
线程数.如果添加线程似乎在帮助吞吐量,
线程池添加更多;否则,它减少了数量
工作线程.这种技术被称为爬山启发式.
因此,将个别任务缩短的一个原因是避免
“饥饿检测”,但另一个原因要保持短路是
给线程池更多的机会提高吞吐量
调整线数.个人的持续时间越短
任务越多,线程池可以测量吞吐量越高
相应地调整线数.

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

500工作线程有什么问题?原则上,没有,如果
你有500个核心可供他们使用和大量的系统
记忆.事实上,这是并行计算的长远愿景.
但是,如果您的计算机上没有这么多核心,那么您就可以
在许多线程竞争时间片的情况下.这个
情况称为处理器超额认购.允许很多
处理器密集的线程在单个核心上竞争时间
上下文切换开销,可以严重降低整体系统
吞吐量.即使你没有用尽内存,在这方面的表现
情况可能比顺序计算差很多.
(每个上下文切换需要6,000到8,000个处理器周期.)
上下文切换的成本不是开销的唯一来源.
.NET中的受管线程消耗大约一兆字节的堆栈
空间,无论该空间是否用于当前执行的功能.
创建新线程需要大约20万个CPU周期
退出一个线程约100,000次循环.这些都是昂贵的操作.

只要你的任务每次都不需要几分钟,线程池就可以了
爬山算法最终会实现它有太多的线程
并自行裁减.但是,如果你有任务
占用工作线程数秒钟或数小时,即可
会抛出线程池的启发式,在这一点上
应该考虑一个选择.

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

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

转载注明原文:任务并行库 – 任务并行库(或PLINQ)是否考虑其他进程? - 代码日志