Java线程和核心数

65

我有一个关于处理器和线程如何工作的问题。根据我的理解,一个核心一次只能执行1个进程。但是我们能够产生一个线程池(比如30个),这个数字比我们拥有的核心数量(比如4个)大,并让它们同时运行。如果我们只有4个核心,这怎么可能呢?我还能在本地电脑上运行我的30个线程程序并继续做其他活动,比如看电影或浏览互联网。

我曾经读过某个地方关于线程调度的内容,这种方法似乎可以让这30个线程在4个核心上并发运行。这是真的吗?如果是,有人能解释一下如何实现并推荐一些好的阅读材料吗?

感谢您的帮助。


4
哇,谢谢你。这些都是非常好的回答,真的帮助我更好地理解了这个主题。 - user5765683
4个回答

111

进程 vs 线程

在古老的日子里,每个进程都只有一个执行线程,所以进程直接被调度到核心上(在那个古老的年代,几乎只有一个核心)。然而,在支持多线程的操作系统中(几乎所有现代操作系统都支持),进行调度的是线程,而不是进程。因此,在此后的讨论中,我们将仅谈论线程,并且您应该理解每个运行中的进程都有一个或多个执行线程。

并行 vs 并发

当两个线程同时运行时,它们就在并行运行。例如,如果我们有两个线程A和B,则它们的并行执行如下所示:

CPU 1:A ------------------------->

CPU 2:B ------------------------->

当两个线程并发运行时,它们的执行交叉。交叉可以通过两种方式之一发生:要么线程同时执行(即像上面那样并行),要么它们的执行在处理器上被交错,如下所示:

CPU 1:A -----------> B ----------> A -----------> B ---------->

因此,对于我们的目的,并行可以被认为是并发的一种特殊情况*

调度

但是我们可以创建一个线程池(假设有30个线程),比我们拥有的核心数量(假设有4个核心)要多,并让它们同时运行。如果我们只有4个核心,这怎么可能呢?

在这种情况下,它们可以同时运行,因为CPU调度程序正在给这30个线程中的每一个分配一些CPU时间。一些线程将会并行运行(如果您有4个核心,则任何时候将有4个线程同时运行),但是所有30个线程都将同时运行。您可以进行游戏或浏览网页的原因是,这些新线程被添加到线程池/队列中,并且也被分配了一定比例的CPU时间。

逻辑核心与物理核心

根据我的理解,一个核心一次只能执行1个进程

这不完全正确。由于非常聪明的硬件设计和流水线技术,这里不便详述(而且我也不太理解),一个物理核心实际上可以同时执行两个完全不同的线程。如果需要,仔细思考一下这句话——它仍然让我感到震惊。

这项令人惊叹的技术被称为Simultaneous Multi-Threading(同时多线程),也被广泛称为Hyper-Threading。因此我们有物理核心,它是实际的硬件CPU核心,以及逻辑核心,它是操作系统告诉软件可供使用的核心数。逻辑核心本质上是一种抽象。在典型的现代Intel CPU中,每个物理内核作为两个逻辑内核。

可以有人解释一下这是如何工作的,并推荐一些好的阅读材料吗?

如果您真的想了解进程、线程和调度是如何协同工作的,我建议您阅读《操作系统概念》。

  • 术语并行并发的确切含义存在激烈的争论,即使在我们自己的堆栈溢出网站这里也是如此。这些术语的含义在很大程度上取决于应用领域。

25

Java不执行线程调度,它将此任务留给操作系统来执行线程调度。

对于计算密集型任务,建议将线程池大小设置为可用核心数。但对于I/O绑定任务,我们应该有更多的线程。如果两种类型的任务都可用并需要CPU时间片,则有许多其他变化。

一个核心一次只能执行1个进程

是的,但它们可以多任务处理并创建一个幻觉,即它们正在同时处理多个进程。

如果我们只有4个核心,这怎么可能呢?我也能在我的本地计算机上运行30个线程程序,并继续执行其他活动

这是由于多任务(也就是并发性)所实现的。假设你启动了30个线程,操作系统也在运行50个线程,所有80个线程将通过逐个获取CPU时间片(每次一个核心上的一个线程)来共享4个CPU核心。这意味着平均每个核心将同时运行80/4=20个线程。你会感觉到所有的线程/进程都在同时运行。

有人能解释一下这是如何工作的吗?

所有这些都是在操作系统层面发生的。如果你是程序员,那么你不用担心这些。但如果你是操作系统的学生,那么可以选择任何一本操作系统书籍,详细了解操作系统级别的多线程或查找一些好的研究论文。你应该知道的一件事是,每个操作系统以不同的方式处理这些事情(但通常概念是相同的)。

有一些语言,比如Erlang,它们使用绿色线程(或进程),因此它们具有将线程映射和调度到自己身上的能力,从而消除了操作系统的影响。因此,如果你感兴趣,也可以对绿色线程进行一些研究。

注意:您还可以研究actors,这是另一种对线程的抽象。像Erlang、Scala等语言使用actors来完成任务。一个线程可以有数百个actors;每个actor可以执行不同的任务(类似于Java中的线程)。

这是一个非常广泛和活跃的研究课题,有很多东西需要学习。


谢谢您的回复。我一定会去了解Erlang并对绿色线程进行更多的研究。 - user5765683

12

简而言之,你对核心的理解是正确的。 一个核心同时只能执行1个线程(也称为进程)。

但是,你的程序并不是真的同时运行30个线程。 在这30个线程中,只有4个在同时运行,其他26个则在等待。 CPU会调度线程并给每个线程一段时间片来在核心上运行。 因此,CPU会让所有线程轮流运行。

一个常见的误解:

拥有更多的线程将使我的程序运行更快。

错误:拥有更多的线程不总是会使你的程序运行得更快。 这只意味着CPU必须做更多的切换,事实上,拥有过多的线程会因为切换出所有不同的进程而导致程序运行变慢。


2
我想对gardenhead的出色回答做出补充。
据我所知,即使在同时多线程(Intel的超线程是其实现之一)中,运行在特定核心上的线程也需要来自同一进程。原因是每个进程都有自己的虚拟地址空间。当这些线程想要访问内存时,它们会使用虚拟地址进行访问。但是,如果这些线程来自不同的进程,则它们会使用不同的虚拟地址引用相同的物理地址(这就是具有不同虚拟地址空间的含义)。因此,这是行不通的。
现在让我们谈论一种特定的同时多线程实现:超线程。这使得每个核心可以同时安排两个线程(来自同一进程)。这是通过简单地添加第二组体系结构寄存器来实现的。请记住,这些包括特殊寄存器,如指令指针和堆栈指针。现在,在每个时钟周期中,核心可以决定从哪个线程获取指令,因为它拥有执行任何两个线程的指令所需的所有信息。
  • 多亏了指令指针,它知道需要获取哪个指令
  • 多亏了堆栈指针,当寄存器已满时,它知道在哪里放置变量
  • 假设这些线程实际上来自同一个进程:它们共享一个堆,因此无论指令来自哪个线程,核心也知道在哪里放置较大的数据结构

通常,当发生内存访问或数据依赖强制执行空操作时,核心会切换到另一个线程的指令。因此,第二个线程可以减少CPU流水线中的气泡。当然,这意味着我们不能期望2倍的加速 - 但是我们可以以很小的成本增加一组体系结构寄存器并添加一些逻辑来决定我们需要选择哪个指令指针等等。英特尔表示,根据应用程序的不同,大约可以提高20%的速度。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接