简体   繁体   English

Task.Run 是如何受 CPU 内核限制的?

[英]How is Task.Run limited by CPU cores?

Why is it that the following program will only run a limited number of blocked tasks.为什么下面的程序只会运行有限数量的阻塞任​​务。 The limiting number seems to be the number of cores on the machine.限制数量似乎是机器上的核心数量。

Initially when I wrote this I expected to see the following:最初,当我写这篇文章时,我希望看到以下内容:

  • Job complete output of Jobs 1 - 24作业 1 - 24 的作业完成输出
  • A 2 second gap 2秒的差距
  • Output of Jobs 25 - 48工作 25 - 48 的产出

However the output was:但是输出是:

  • Job complete output of Jobs 1 - 4作业 1 - 4 的作业完成输出
  • Then randomly completing jobs every couple of 100ms.然后每隔 100 毫秒随机完成一次作业。

When running on server with 32 cores, the program did run as I had expected.在具有 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());
    }
}

However replacing the Thread.Sleep with Task.Delay gives my expected results.但是用 Task.Delay 替换 Thread.Sleep 给出了我预期的结果。

    public async Task Schedule()
    {
        await _semaphore.WaitAsync();

        Task.Delay(2000)
            .ContinueWith(t => _semaphore.Release());
    }

And using a Thread gives my expected results并使用Thread给出了我预期的结果

    public async Task Schedule()
    {
        await _semaphore.WaitAsync();

        var thread = new Thread(() =>
        {
            Thread.Sleep(2000);
            _semaphore.Release();
        });
        thread.Start();
    }

How does Task.Run() work? Task.Run()如何工作? Is it the case it is limited to the number of cores?是不是有核数限制?

Task.Run schedules the work to run in the thread pool. Task.Run调度工作在线程池中运行。 The thread pool is given wide latitude to schedule the work as best as it can in order to maximize throughput.线程池被赋予了很大的自由度来尽可能地安排工作,以最大限度地提高吞吐量。 It will create additional threads when it feels they will be helpful, and remove threads from the pool when it doesn't think it will be able to have enough work for them.它会在觉得有用时创建额外的线程,并在它认为无法为它们提供足够的工作时从池中删除线程。

Creating more threads than your processor is able to run at the same time isn't going to be productive when you have CPU bound work.当您有 CPU 密集型工作时,创建多于您的处理器能够同时运行的线程不会有效率。 Adding more threads will just result in dramatically more context switches, increasing overhead, and reducing throughput.添加更多线程只会导致更多的上下文切换、增加开销并降低吞吐量。

Yes for compute bound operations Task.Run() internally uses CLR's thread pool which will throttle the number of new threads to avoid CPU over-subscription.是的,对于计算绑定操作Task.Run()内部使用 CLR 的线程池,它将限制新线程的数量以避免 CPU 过度订阅。 Initially it will run the number of threads that equals to the number of cpu cores concurrently.最初,它会并发运行等于 cpu 内核数的线程数。 Then it continually optimises the number of threads using a hill-climbing algorithm based on factors like the number of requests thread pool receives and overall computer resources to either create more threads or fewer threads.然后,它根据线程池接收的请求数量和整体计算机资源等因素,使用爬山算法不断优化线程数量,以创建更多线程或更少线程。

In fact, this is one of the main benefits of using pooled thread over raw thread eg ( new Thread(() => {}).Start() ) as it not only recycles threads but also optimises performance internally for you.事实上,这是使用池线程而不是原始线程的主要好处之一,例如( new Thread(() => {}).Start() ),因为它不仅可以回收线程,还可以在内部为您优化性能。 As mentioned in the other answer, it's generally a bad idea to block pooled threads because it will "mislead" thread pool's optimisation, simiarly using many pooled thread to do very long-running computation can also lead to thread pool creating more threads and consequently increase the overheads of context switch and later destory extra threads in the pool.正如在另一个答案中提到的,阻塞池线程通常是一个坏主意,因为它会“误导”线程池的优化,同样地,使用许多池线程来执行非常长时间运行的计算也会导致线程池创建更多线程并因此增加上下文切换和稍后销毁池中额外线程的开销。

The Task.Run() is running based on CLR Thread pool. Task.Run() 基于 CLR 线程池运行。 There is a concept called 'OverSubscription', means there are being more active thread than CPU Cores and they must be time-sliced.有一个叫做“OverSubscription”的概念,意味着比 CPU 内核有更多的活动线程,它们必须是时间切片的。 In Thread-Pool when the number of threads that must be scheduled on CPU Cores increase, Context-Switch rise and at the result, the performance will hurt.在 Thread-Pool 中,当必须在 CPU 内核上调度的线程数量增加时,Context-Switch 上升,结果会损害性能。 The CLR that manages the Thread-Pool avoid OverSubscription by queuing and throttling the thread startup and always try to compensate workload.管理线程池的 CLR 通过排队和限制线程启动来避免过度订阅,并始终尝试补偿工作负载。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM