任务并行库(或 PLINQ)是否考虑了其他进程? [英] Does the Task Parallel Library (or PLINQ) take other processes into account?

查看:31
本文介绍了任务并行库(或 PLINQ)是否考虑了其他进程?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

特别是,我正在考虑使用 TPL 来启动(并等待)外部进程.在决定开始另一个任务(因此——在我的例子中——另一个外部进程)之前,TPL 是否会查看机器总负载(CPU 和 I/O)?

In particular, I'm looking at using TPL to start (and wait for) external processes. Does the TPL look at total machine load (both CPU and I/O) before deciding to start another task (hence -- in my case -- another external process)?

例如:

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

I've got about 100 media files that need to be encoded or transcoded (e.g. from WAV to FLAC or from FLAC to MP3). The encoding is done by launching an external process (e.g. FLAC.EXE or LAME.EXE). Each file takes about 30 seconds. Each process is mostly CPU-bound, but there's some I/O in there. I've got 4 cores, so the worst case (transcoding by piping the decoder into the encoder) still only uses 2 cores. I'd like to do something like:

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

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

Will this kick off 100 tasks (and hence 200 external processes competing for the CPU)? Or will it see that the CPU's busy and only do 2-3 at a time?

推荐答案

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

You're going to run into a couple of issues here. The starvation avoidance mechanism of the scheduler will see your tasks as blocked as they wait on processes. It will find it hard to distinguish between a deadlocked thread and one simply waiting for a process to complete. As a result it may schedule new tasks if your tasks run or a long time (see below). The hillclimbing heuristic should take into account the overall load on the system, both from your application and others. It simply tries to maximize work done, so it will add more work until the overall throughput of the system stops increasing and then it will back off. I don't think this will effect your application but the stavation avoidance issue probably will.

您可以在使用 Microsoft®.NET 进行并行编程、Colin Campbell、Ralph Johnson、Ade Miller、Stephen Toub(较早的草稿是在线).

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

"The .NET thread pool automatically manages the number of worker threads in the pool. It adds and removes threads according to built-in heuristics. The .NET thread pool has two main mechanisms for injecting threads: a starvation-avoidance mechanism that adds worker threads if it sees no progress being made on queued items and a hillclimbing heuristic that tries to maximize throughput while using as few threads as possible.

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

The goal of starvation avoidance is to prevent deadlock. This kind of deadlock can occur when a worker thread waits for a synchronization event that can only be satisfied by a work item that is still pending in the thread pool’s global or local queues. If there were a fixed number of worker threads, and all of those threads were similarly blocked, the system would be unable to ever make further progress. Adding a new worker thread resolves the problem.

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

A goal of the hill-climbing heuristic is to improve the utilization of cores when threads are blocked by I/O or other wait conditions that stall the processor. By default, the managed thread pool has one worker thread per core. If one of these worker threads becomes blocked, there’s a chance that a core might be underutilized, depending on the computer’s overall workload. The thread injection logic doesn’t distinguish between a thread that’s blocked and a thread that’s performing a lengthy, processor-intensive operation. Therefore, whenever the thread pool’s global or local queues contain pending work items, active work items that take a long time to run (more than a half second) can trigger the creation of new thread pool worker threads.

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

The .NET thread pool has an opportunity to inject threads every time a work item completes or at 500 millisecond intervals, whichever is shorter. The thread pool uses this opportunity to try adding threads (or taking them away), guided by feedback from previous changes in the thread count. If adding threads seems to be helping throughput, the thread pool adds more; otherwise, it reduces the number of worker threads. This technique is called the hill-climbing heuristic. Therefore, one reason to keep individual tasks short is to avoid "starvation detection," but another reason to keep them short is to give the thread pool more opportunities to improve throughput by adjusting the thread count. The shorter the duration of individual tasks, the more often the thread pool can measure throughput and adjust the thread count accordingly.

为了具体说明这一点,请考虑一个极端的例子.认为您有一个复杂的金融模拟,其中包含 500 个处理器密集型操作,每个操作平均需要十分钟去完成.如果您在全局队列中为每个创建顶级任务在这些操作中,您会发现大约五分钟后线程池将增长到 500 个工作线程.原因是线程池将所有任务视为阻塞并开始添加新任务线程的速度大约为每秒两个线程.

To make this concrete, consider an extreme example. Suppose that you have a complex financial simulation with 500 processor-intensive operations, each one of which takes ten minutes on average to complete. If you create top-level tasks in the global queue for each of these operations, you will find that after about five minutes the thread pool will grow to 500 worker threads. The reason is that the thread pool sees all of the tasks as blocked and begins to add new threads at the rate of approximately two threads per second.

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

What’s wrong with 500 worker threads? In principle, nothing, if you have 500 cores for them to use and vast amounts of system memory. In fact, this is the long-term vision of parallel computing. However, if you don’t have that many cores on your computer, you are in a situation where many threads are competing for time slices. This situation is known as processor oversubscription. Allowing many processor-intensive threads to compete for time on a single core adds context switching overhead that can severely reduce overall system throughput. Even if you don’t run out of memory, performance in this situation can be much, much worse than in sequential computation. (Each context switch takes between 6,000 and 8,000 processor cycles.) The cost of context switching is not the only source of overhead. A managed thread in .NET consumes roughly a megabyte of stack space, whether or not that space is used for currently executing functions. It takes about 200,000 CPU cycles to create a new thread, and about 100,000 cycles to retire a thread. These are expensive operations.

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

As long as your tasks don’t each take minutes, the thread pool’s hill-climbing algorithm will eventually realize it has too many threads and cut back on its own accord. However, if you do have tasks that occupy a worker thread for many seconds or minutes or hours, that will throw off the thread pool’s heuristics, and at that point you should consider an alternative.

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

The first option is to decompose your application into shorter tasks that complete fast enough for the thread pool to successfully control the number of threads for optimal throughput. A second possibility is to implement your own task scheduler object that does not perform thread injection. If your tasks are of long duration, you don’t need a highly optimized task scheduler because the cost of scheduling will be negligible compared to the execution time of the task. MSDN® developer program has an example of a simple task scheduler implementation that limits the maximum degree of concurrency. For more information, see the section, "Further Reading," at the end of this chapter.

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

As a last resort, you can use the SetMaxThreads method to configure the ThreadPool class with an upper limit for the number of worker threads, usually equal to the number of cores (this is the Environment.ProcessorCount property). This upper limit applies for the entire process, including all AppDomains."

这篇关于任务并行库(或 PLINQ)是否考虑了其他进程?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆