TPL 将并发程度动态调整为最大
有效地使用所有可用的处理器内核。其他
TPL 的好处是,您不必处理线程
创建和同步。
此声明适用于Parallel.For 系列 TPL API。它不适用于Task.Run 或Task.Factory.StartNew,您可以明确控制并行度。
对于Task.Run(以及带有默认选项的Task.Factory.StartNew),没有智能“缩放”。这只是简单的循环执行工作项,就像ThreadPool.QueueUserWorkItem 一样。这实际上可能会占用所有可用的池线程(最多ThreadPool.GetMaxThreads),然后在繁忙的池线程可用时将新任务排队等待延迟执行。也可能是the thread pool stuttering issue的主题。
使用Task.Factory.StartNew 和LongRunning 不同之处仅在于您可以避免线程池卡顿问题,但最终您可能会简单地耗尽操作系统内存和其他资源,因为操作系统线程是一种非常昂贵的资源。
在Parallel.For 等情况下,TPL 调度程序更加智能。它不会在每个工作项一个线程的基础上浪费线程。相反,它具有相当复杂的命令式逻辑,考虑到 CPU/内核的数量以及可能的其他一些运行时指标。
更新以解决评论,这里有一个简单的例子:
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
int max = 50;
int delay = 30; // ~30s per work item
ThreadPool.SetMaxThreads(max, max);
Console.WriteLine("starting, threads: {0}", Process.GetCurrentProcess().Threads.Count);
var tasks = Enumerable.Range(0, max).Select(n => Task.Factory.StartNew(() =>
{
Console.WriteLine("task: {0}, threads: {1}, pool thread: {2}",
n, Process.GetCurrentProcess().Threads.Count, Thread.CurrentThread.IsThreadPoolThread);
for (int i = 0; i < delay * 1000; i++)
{
Thread.Sleep(1);
}
})).ToArray();
Console.WriteLine("waiting, threads: {0}", Process.GetCurrentProcess().Threads.Count);
Task.WaitAll(tasks);
Console.WriteLine("done, threads: {0}", Process.GetCurrentProcess().Threads.Count);
Console.ReadLine();
}
}
}
输出(发布版本,未附加调试器,.NET 4.5,4 核 CPU):
开始,线程:3
任务:0,线程:11,池线程:True
任务:2,线程:11,池线程:True
等待,线程:11
任务:1,线程:11,池线程:真
任务:3,线程:11,池线程:True
...
任务:48,线程:56,池线程:真
任务:49,线程:57,池线程:True
完成,线程数:47
它确认了ThreadPool 的增长和卡顿行为,最多可达max 线程数。新线程的创建延迟约 500 毫秒。
现在,如果我们将TaskCreationOptions.LongRunning 添加到Task.Factory.StartNew,我们消除了口吃,并且我们不再受ThreadPool 大小的限制,但我们最终仍将参与到max 数字新线程,每个任务一个(取决于每个工作项执行的时间)。
它还会自动/内部调整并发程度
动态地最有效地使用所有处理器内核或
开发者需要编写代码来处理所有这些吗?
因此,如果开发人员想要使用 TPL 的 Task.Run 或 Task.Factory.StartNew API,他或她确实需要手动处理并行级别。不过这并不难,例如,SemaphoreSlim。