ThreadPool.SetMinThreads 不创建任何新线程

Posted

技术标签:

【中文标题】ThreadPool.SetMinThreads 不创建任何新线程【英文标题】:ThreadPool.SetMinThreads does not create any new threads 【发布时间】:2021-10-28 02:06:40 【问题描述】:

我正试图弄清楚ThreadPool.SetMinThreads 的确切影响。

根据official documentation它说

设置线程池在发出新请求时按需创建的最小线程数,然后切换到管理线程创建和销毁的算法。

据我了解,作为一名开发人员,我应该能够控制如何按需旋转新线程的机制,因此它们会在空闲状态下创建并等待,例如在我期待负载的情况下在特定时间收到的请求。

这正是我最初认为SetMinThreads 方法的用途。

但是当我开始实际使用它时 - 我得到了非常奇怪的结果。

所以我有我的 ASP.NET .NET5 应用程序,并且在控制器操作中我有这样的代码: ThreadPool.SetMinThreads(32000, 1000);

当然,我直观地期望运行时为我创建 32K 的工作线程和 1000 个 io 线程。

当我这样做时,然后调用其他方法 - Process.GetCurrentProcess().Threads 来获取所有进程的线程,并在它们上打印统计信息,我会得到类似的结果

Standby - 17
Running - 4

我认为也许应用程序需要一些时间来旋转新线程,所以我尝试了不同的延迟,1 分钟、5 分钟和 10 分钟。

但结果始终保持不变,我得到 15-20 Standby2-4 Running

那么逻辑问题就来了——SetMinThreads 方法到底在做什么? MSDN 提供的描述似乎不是很有帮助。

还有一个合乎逻辑的问题——如果我想强制 dotnet 在空闲状态下旋转 32K 新线程怎么办——dotnet 是否提供了任何机制呢?

【问题讨论】:

您的计算机甚至能够创建这么多线程的几率非常很低。如果您以某种方式拥有一台可以实际运行它们的计算机,那将是一个好主意的可能性要低得多。如果您想对线程进行微观管理而不使用线程池,那么线程池的全部意义在于显式管理线程。这是游泳池的工作。 是的,我想这是有道理的......我想我只是不了解可以相对快速地创建数千个线程,所以我的情况是突然加载 - 它可以处理,并且没有需要提前创建空闲线程。 【参考方案1】:

ThreadPool.SetMinThreads 设置ThreadPool 立即按需创建的最小线程数。这是关键短语,确实很不直观。 ThreadPool 目前¹ (.NET 5) 在两种模式下工作:

    当一个新的工作请求到达,并且池中的所有线程都忙时,立即创建一个新线程以满足该请求。

    当一个新的工作请求到达,并且池中的所有线程都忙时,将请求排队,并在创建新线程之前等待 1 秒,希望同时其中一个工作线程能够完成其当前工作,并将可用于处理排队的请求。

ThreadPool.SetMinThreads 设置这两种模式之间的阈值。它确实让您可以控制当前活动的线程数。这不是很令人满意,但它就是这样。如果你想强制ThreadPool 立即创建 1000 个线程,除了调用ThreadPool.SetMinThreads(1000, 1000) 之外,你还必须发送相同数量的工作请求。像这样的东西应该可以解决问题:

ThreadPool.SetMinThreads(1000, 1000);
Task[] tasks = Enumerable.Range(0, 1000)
    .Select(_ => Task.Run(() => Thread.Sleep(100)))
    .ToArray();

老实说,我认为没有人这样做。创建一个新的Thread 在人工时间上是相当快的(我的 PC 中每个线程需要大约 0.25 毫秒),因此对于接收来自人类的请求的系统,创建线程的开销应该有任何可衡量的影响。另一方面,0.25 毫秒是计算机时间的一个 eon,当您希望线程执行少量工作(在纳秒范围内)时,例如在 List<T> 中添加一些内容。这就是最初发明 ThreadPool 的原因:为微小但大量的工作负载分摊线程创建的开销。

请注意,创建新的Thread 也有内存成本,这通常比时间成本更重要:每个线程的堆栈至少需要1 MB 的 RAM。因此,创建 32,000 个线程将占用 32 GB 的内存,仅用于堆栈空间。这不是很有效。这就是为什么近年来asynchronous programming 在服务器端 Web 开发中如此突出的原因,因为它允许用更少的线程完成更多的工作。

¹ 没有什么能阻止微软工程师在未来改变/调整ThreadPool 的实现。 AFAIK 这在过去至少发生过一次。

【讨论】:

据我所知,两个线程之间的延迟为 1 秒。你从哪里得到500ms?使用 .NET 6,如果所有线程都阻塞等待 Task.Result,则可以更快地启动新线程。 @AloisKraus you are right,1 秒。我可以发誓它是 500 毫秒,基于过去的许多观察。可能我误解了我过去的许多实验。 让我直说。因此,如果我将最小工作线程设置为 1000,并且我目前有 5 个工作线程 - 我们将在模式 #1 下工作,并且将为每个请求旋转新线程。但是一旦我的线程池中达到 1000 个工作线程——我们将切换到模式 #2,每个新请求都会等待来自线程池的线程一秒钟——然后才创建新线程。这是正确的想法吗? @Prostakov 是的,AFAIK 这就是它的工作原理。

以上是关于ThreadPool.SetMinThreads 不创建任何新线程的主要内容,如果未能解决你的问题,请参考以下文章

尤娜系统的第一次飞行中换引擎的架构垂直拆分改造

如何增加 .NET Remoting over TCP 使用的线程数?