一尘不染

处理器核心数量与线程池大小的关系

java

许多次我听说最好将线程池中的线程数保持在该系统中的内核数以下。具有比核心数多两倍或更多的线程不仅浪费,而且还可能导致性能下降。

那些是真的吗?如果不是,那么揭露这些主张的基本原则是什么(特别是与Java有关)?


阅读 477

收藏
2020-12-03

共1个答案

一尘不染

许多次我听说最好将线程池中的线程数保持在该系统中的内核数以下。具有比核心数多两倍或更多的线程不仅浪费,而且还可能导致性能下降。

这些主张 作为一般性陈述 是不正确的。就是说,有时它们是正确的(或者说是真实的),而有时它们显然是错误的。

有几件事是正确的:

  1. 更多线程意味着更多内存使用。每个线程都需要一个线程堆栈。对于最新的HotSpot JVM, 最小 线程堆栈大小为64Kb,默认值可能高达1Mb。那可能很重要。此外,任何活动线程都可能拥有或共享堆中的对象,无论该线程当前是否可运行。因此,有理由期望更多的线程意味着更大的内存工作集。

  2. JVM实际运行的线程数不能超过执行硬件上的核心(或超线程核心等)。没有引擎,汽车就不会运转;没有核心,线程就不会运转。

除此之外,事情变得不那么明确了。“问题”是活动线程可能处于各种“状态”。例如:

  • 活动线程可以正在运行;即积极执行指令。
  • 活动线程可以运行;即等待一个核心,以便它可以运行。
  • 一个活动线程可以通过同步来实现;即等待来自另一个线程的信号,或等待释放锁。
  • 实时线程可能正在等待外部事件。例如,等待某些外部服务器/服务响应请求。

“每核一个线程”启发式方法假定线程正在运行或可运行(根据上述内容)。但是对于许多多线程应用程序,启发式方法是错误的……因为它没有考虑其他状态下的线程。

现在,“太多”线程显然 可以 导致明显的性能下降,这很简单, 只需
使用太多内存即可。(想象一下,您有4Gb的物理内存,并且使用1Mb的堆栈创建了8,000个线程。这就是解决虚拟内存问题的方法。)

但是其他事情呢?线程过多 会导致 过多的上下文切换吗?

我不这么认为。如果你有大量的线程,和你的应用程序使用的线程可能会导致过多的上下文切换,这
不好的性能。但是,我认为上下文切换的根本原因不是实际的线程数。性能问题的根源很可能是该应用程序是:

  • 以特别浪费的方式进行同步;例如使用Object.notifyAll()when Object.notify()会更好,或者
  • 在竞争激烈的数据结构上进行同步,或者
  • 相对于每个线程正在执行的有用工作量,执行的同步过多,或者
  • 尝试并行执行过多的I / O。

(在最后一种情况下,瓶颈可能是I / O系统,而不是上下文切换…,除非I / O是IPC,并且服务/程序在同一台计算机上。)

另一点是,在没有上述混杂因素的情况下,拥有更多线程不会增加上下文切换。如果您的应用程序有N个可运行的线程与M个处理器竞争,并且这些线程是完全无计算和争用的,则OS的线程调度程序将尝试在它们之间进行时间分段。但是时间片的长度很可能以十分之一秒(或更长时间)为单位进行度量,因此与CPU绑定线程在其分片期间实际执行的工作相比,上下文切换开销可以忽略不计。如果我们假设一个时间片的长度是恒定的,那么上下文切换的开销也将是恒定的。添加更多可运行线程(增加N)不会显着改变工作与开销的比率。


总而言之,“线程过多”对性能有害。但是,对于多少是“太多”,没有可靠的通用“经验法则”。并且(幸运的是)在“太多”的性能问题变得严重之前,您通常还有很大的余地。

2020-12-03