为什么以下程序只运行有限数量的被阻止任务。限制数似乎是机器上的核心数。
最初,当我写这篇文章时,我希望看到以下内容:
然而输出是:
在具有32个内核的服务器上运行时,程序确实按照我的预期运行。
class Program
{
private static object _lock = new object();
static void Main(string[] args)
{
int completeJobs = 1;
var limiter = new MyThreadLimiter();
for (int iii = 1; iii < 100000000; iii++)
{
var jobId = iii;
limiter.Schedule()
.ContinueWith(t =>
{
lock (_lock)
{
completeJobs++;
Console.WriteLine("Job: " + completeJobs + " scheduled");
}
});
}
Console.ReadLine();
}
}
class MyThreadLimiter
{
readonly SemaphoreSlim _semaphore = new SemaphoreSlim(24);
public async Task Schedule()
{
await _semaphore.WaitAsync();
Task.Run(() => Thread.Sleep(2000))
.ContinueWith(t => _semaphore.Release());
}
}
但是,用Task.Delay替换Thread.Sleep可以得到我期望的结果。
public async Task Schedule()
{
await _semaphore.WaitAsync();
Task.Delay(2000)
.ContinueWith(t => _semaphore.Release());
}
使用Thread
给出了我的预期结果
public async Task Schedule()
{
await _semaphore.WaitAsync();
var thread = new Thread(() =>
{
Thread.Sleep(2000);
_semaphore.Release();
});
thread.Start();
}
Task.Run()
如何运作?它是否仅限于核心数量?
答案 0 :(得分:7)
Task.Run
计划在线程池中运行的工作。线程池具有广泛的自由度,可以尽可能地调度工作,以便最大化吞吐量。它会在感觉有用的时候创建额外的线程,并且当它认为它不能为它们提供足够的工作时从池中删除线程。
当你有CPU绑定工作时,创建比处理器能够同时运行的更多线程并不会有效。添加更多线程只会导致更多的上下文切换,增加开销并降低吞吐量。
答案 1 :(得分:2)
是的,对于计算绑定操作Task.Run()
在内部使用CLR的线程池,这将限制新线程的数量以避免CPU超额预订。最初它将同时运行等于cpu核心数的线程数。然后,它根据线程池接收的请求数量和整体计算机资源等因素,使用爬山算法不断优化线程数,以创建更多线程或更少线程。
事实上,这是在原始线程上使用池化线程的主要好处之一,例如(new Thread(() => {}).Start()
)因为它不仅可以回收线程,还可以为您内部优化性能。正如在另一个答案中所提到的,阻止汇集线程通常是一个坏主意,因为它会误导&#34;误导&#34;线程池的优化,同样使用许多池化线程来执行非常长时间运行的计算也会导致线程池创建更多线程,从而增加了上下文切换的开销以及后来池中的额外线程。
答案 2 :(得分:1)
Task.Run()正在基于CLR线程池运行。 有一个叫做“过度订阅”的概念 线程比CPU内核更活跃,并且必须按时间划分。 在“线程池”中,当必须在CPU内核上调度的线程数时 增加,上下文切换增加,结果将损害性能。 管理线程池的CLR通过排队避免了过度订阅 并限制线程启动,并始终尝试补偿工作量。