19

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

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

4

4 回答 4

27

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

作为一般性声明,这些说法并不正确。也就是说,有时它们是真实的(或真实的),有时它们显然是错误的。

有几件事是毋庸置疑的:

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

  2. JVM 实际运行的线程不能多于执行硬件上的内核(或超线程内核或其他)。汽车没有引擎就无法运行,线程没有核心就无法运行。

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

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

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

现在,“太多”线程显然导致性能显着下降,这很简单,就是使用太多内存。(想象一下,您有 4Gb 的物理内存,并创建了 8,000 个线程和 1Mb 的堆栈。这就是虚拟内存抖动的秘诀。)

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

我不这么认为。如果您有很多线程,并且您的应用程序使用这些线程可能会导致过多的上下文切换,这对性能不利。但是,我认为上下文切换的根本原因不是线程的实际数量。性能问题的根源更有可能是应用程序:

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

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

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


综上所述,“线程过多”确实对性能有害。然而,对于有多少是“太多”,并没有可靠的通用“经验法则”。而且(幸运的是)在“太多”的性能问题变得严重之前,您通常有相当大的余地。

于 2013-01-28T06:45:58.977 回答
7

线程数少于内核数通常意味着您无法利用所有可用的内核数。

通常的问题是你想要的线程比内核多多少。但是,这会有所不同,具体取决于线程花费在 I/O 之类的事情上的时间(总体)与它们花费在计算上的时间。如果它们都在进行纯计算,那么您通常需要与内核数量相同的线程。如果他们正在执行大量 I/O,您通常需要比内核多得多的线程。

从另一个方向看一会儿,您希望运行足够多的线程,以确保每当一个线程因某种原因阻塞(通常等待 I/O)时,您就有另一个线程(未被阻塞)可在该内核上运行。所需的确切数字取决于每个线程花费多少时间阻塞。

于 2013-01-28T05:41:23.353 回答
4

这不是真的,除非线程的数量远远多于内核的数量。原因是额外的线程将意味着额外的上下文切换。但这不是真的,因为操作系统只会在上下文切换有益的情况下进行非强制上下文切换,并且额外的线程不会强制额外的上下文切换。

如果你创建了一个荒谬的线程数,那会浪费资源。但与创建太少线程的糟糕程度相比,这一切都不算什么。如果您创建的线程太少,意外的块(例如页面错误)可能会导致 CPU 处于空闲状态,并且会消除一些额外的上下文切换可能带来的任何危害。

于 2013-01-28T05:41:08.793 回答
2

不完全正确,这取决于整体软件架构。保留比可用内核更多的线程是有原因的,以防某些线程被操作系统挂起,因为它们正在等待 I/O 完成。这可能是显式的 I/O 调用(例如从文件同步读取),也可能是隐式的,例如系统分页处理。

实际上,我在一本书中读到,将线程数保持为 CPU 内核数的两倍是一种很好的做法。

于 2013-01-28T05:42:03.830 回答