为什么完全 CPU 绑定的进程与超线程一起工作得更好?
Why would a fully CPU bound process work better with hyperthreading?
鉴于:
- 完全 CPU 绑定非常大(即超过几个 CPU 周期)作业,并且
- A CPU 具有 4 个物理内核和总共 8 个逻辑内核,
8、16 和 28 个线程的性能是否可能优于 4 个线程?我的理解是 4 个线程执行的上下文切换 较少 并且在任何意义上都会 较少的开销 8, 16或者 28 个线程将在 4 个物理核心机器上 。但是,时间是 -
Threads Time Taken (in seconds)
4 78.82
8 48.58
16 51.35
28 52.10
下面的原始问题部分提到了用于测试获取时间的代码。 CPU规格也在底部给出。
看完各位网友的回答和评论里给出的信息,我终于可以把问题归结为我上面写的。如果上面的问题给了你完整的上下文,你可以跳过下面的原始问题。
原问题
我们说
是什么意思
Hyper-threading works by duplicating certain sections of the
processor—those that store the architectural state—but not duplicating
the main execution resources. This allows a hyper-threading processor
to appear as the usual "physical" processor and an extra "logical"
processor to the host operating system
?
This question 今天在 SO 上被问到,它基本上测试了执行相同工作的多个线程的性能。它有以下代码:
private static void Main(string[] args)
{
int threadCount;
if (args == null || args.Length < 1 || !int.TryParse(args[0], out threadCount))
threadCount = Environment.ProcessorCount;
int load;
if (args == null || args.Length < 2 || !int.TryParse(args[1], out load))
load = 1;
Console.WriteLine("ThreadCount:{0} Load:{1}", threadCount, load);
List<Thread> threads = new List<Thread>();
for (int i = 0; i < threadCount; i++)
{
int i1 = i;
threads.Add(new Thread(() => DoWork(i1, threadCount, load)));
}
var timer = Stopwatch.StartNew();
foreach (var thread in threads) thread.Start();
foreach (var thread in threads) thread.Join();
timer.Stop();
Console.WriteLine("Time:{0} seconds", timer.ElapsedMilliseconds/1000.0);
}
static void DoWork(int seed, int threadCount, int load)
{
var mtx = new double[3,3];
for (var i = 0; i < ((10000000 * load)/threadCount); i++)
{
mtx = new double[3,3];
for (int k = 0; k < 3; k++)
for (int l = 0; l < 3; l++)
mtx[k, l] = Math.Sin(j + (k*3) + l + seed);
}
}
(为了便于阅读,我剪掉了几个大括号将代码放在一个页面中。)
我 运行 我的机器上的这段代码用于复制问题。我的机器有 4 个物理内核和 8 个逻辑内核。上面代码中的方法 DoWork()
是完全 CPU 绑定的。 我觉得超线程可能有助于提高 30% 的速度(因为这里我们有与物理内核一样多的 CPU 绑定线程(即 4))。但它几乎确实获得了 64% 的性能增益。 当我 运行 这段代码用于 4 个线程时,它花费了大约 82 秒,而当我 运行 这段代码用于 8、16 和 28 个线程时,它 运行 在所有情况下大约 50 秒。
时间总结:
Threads Time Taken (in seconds)
4 78.82
8 48.58
16 51.35
28 52.10
我可以看到 4 个线程的 CPU 使用率约为 50%。 不应该是 ~100% 吗? 毕竟我的处理器只有 4 个物理内核。 8 个和 16 个线程的 CPU 使用率约为 100%。
如果有人能在开头解释引用的文字,我希望能更好地理解 超线程,进而希望得到 为什么会这样的答案完全 CPU 绑定的进程与超线程一起工作得更好?。
为了完成,
- 我有英特尔酷睿 i7-4770 CPU @ 3.40 GHz,3401 MHz,4 个核心,8 个逻辑处理器。
- 我运行发布模式下的代码。
- 我知道测量计时的方式很糟糕。这只会给出最慢线程的时间。我从另一个问题中获取了代码。但是,当 运行 4 CPU 绑定线程在 4 物理核心机器上时,50% CPU 使用的理由是什么?
I could see that CPU usage was ~50% with 4 threads. Shouldn't it be ~100%?
不,不应该。
what is the justification for 50% CPU usage when running 4 CPU bound threads on a 4 physical core machine?
这就是 CPU 利用率在 Windows 中的报告方式(顺便说一句,至少在其他一些 OS 中也是如此)。 HT CPU 在操作系统中显示为两个内核,并按此报告。
因此,当您有四个 HT CPU 时,Windows 会看到一个八核机器。如果您查看任务管理器中的 "Performance" 选项卡,您将看到八个不同的 CPU 图表,并且计算总 CPU 利用率时,100% 的利用率是这八个核心的全部利用率.
如果您只使用四个线程,那么这些线程无法充分利用可用的 CPU 资源 ,这就解释了计时 。他们最多可以使用八个可用内核中的四个,因此您的利用率当然会达到 50%。 一旦超过逻辑核心数 (8),运行时间将再次增加;在这种情况下,您正在添加调度开销而不添加任何新的计算资源。
顺便说一下……
HyperThreading 与过去的共享缓存和其他限制相比有了很大改进,但它仍然无法提供与完整 CPU 相同的吞吐量优势,因为 CPU 中仍然存在一些争论=32=]。因此,即使忽略 OS 开销,你 35% 的速度提升对我来说也很不错。我经常看到将额外的 HT 核心添加到计算瓶颈的过程中,速度提升不超过 20%。
超线程通过在处理器执行管道中交错指令来工作。当处理器在一个 'thread' 上执行读写操作时,它正在对另一个 'thread' 执行逻辑评估,将它们分开并让您感觉性能翻倍。
你获得如此大加速的原因是因为你的 DoWork
方法中没有分支逻辑。这都是一个大循环,执行顺序非常可预测。
处理器执行管道必须经过几个时钟周期才能执行单个计算。处理器尝试通过使用接下来的几条指令预加载执行缓冲区来优化性能。如果加载的指令实际上是条件跳转(例如 if
语句),这是个坏消息,因为处理器必须刷新整个流水线并从内存的不同部分获取指令。
您可能会发现,如果将 if
语句放入 DoWork
方法中,您将无法获得 100% 的加速...
我无法解释您观察到的绝对加速量:100% 似乎对超线程的改进太大了。但是我可以把原理解释到位。
超线程的主要好处是当处理器必须在线程之间切换时。每当线程数多于 CPU 个内核时(99.9997% 的时间是正确的)并且 OS 决定切换到不同的线程,它必须执行(大部分)以下步骤:
- 保存当前线程的状态:这包括堆栈、寄存器的状态和程序计数器。它们保存的位置取决于体系结构,但一般来说,它们要么保存在缓存中,要么保存在内存中。无论哪种方式,这一步都需要时间。
- 将线程置于 "Ready" 状态(与 "Running" 状态相反)。
- 加载下一个线程的状态:同样,包括堆栈、寄存器和程序计数器,这又是一个步骤,需要时间。
- 将线程翻转到 "Running" 状态。
在普通(非HT)CPU中,它拥有的核心数就是处理单元的数量。其中每一个都包含寄存器、程序计数器(寄存器)、堆栈计数器(寄存器)、(通常)单独的高速缓存和完整的处理单元。所以如果一个普通的 CPU 有 4 个核心,它可以同时 运行 4 个线程。当一个线程完成时(或者 OS 认为它花费了太多时间并且需要等待轮到它再次启动),CPU 需要遵循这四个步骤来卸载线程并加载在新的执行之前可以开始新的。
另一方面,在超线程CPU中,上述情况成立,但除此之外,每个内核都有一组重复的寄存器、程序计数器、堆栈计数器和(有时)缓存。这意味着 4 核 CPU 仍然只能同时拥有 4 个线程 运行ning,但是 CPU 可以同时拥有 "preloaded" 个线程重复的寄存器。所以 4 个线程是 运行ning,但是 8 个线程被加载到 CPU,4 个活跃,4 个不活跃。然后,当 CPU 需要切换线程时,不必在线程需要切换时执行 loading/unloading,只需 "toggles" 哪个线程处于活动状态,并且在新 "inactive" 寄存器的后台执行 unloading/loading。还记得我用 "these steps take time" 后缀的两个步骤吗?在超线程系统中,步骤 2 和 4 是唯一需要实时执行的步骤,而步骤 1 和 3 在硬件后台执行(脱离任何线程或进程的概念或 CPU 核心)。
现在,这个过程并不能完全加速多线程软件,但在线程经常执行非常小的工作负载的环境中,线程切换的数量可能会很昂贵。即使在不符合该范例的环境中,也可以从超线程中获益。
如果您需要任何说明,请告诉我。 CS250 已经有几年了,所以我可能会在这里或那里混淆术语;如果我对某事使用了错误的术语,请告诉我。我 99.9997% 确定我所描述的一切在其运作逻辑方面都是准确的。
CPU管道
每条指令都必须经过pipeline中的几个步骤才能完全执行。至少,它必须被解码,发送到执行单元,然后在那里实际执行。现代CPUs上有几个执行单元,它们可以完全并行地执行指令。顺便说一句,执行单元不可互换:某些操作只能在单个执行单元上完成。例如,内存加载通常专门针对一个或两个单元,内存存储专门发送给另一个单元,所有计算都由其他单元完成。
了解流水线,我们可能会想:如果我们编写纯顺序代码并且每条指令都必须经过这么多流水线阶段,CPU 怎么能工作得这么快?答案如下:处理器以 out-of-order 方式执行指令。它有一个很大的重新排序缓冲区(例如,用于 200 条指令),并通过其管道并行推送许多指令。如果在任何时候某些指令由于任何原因无法执行(等待来自慢速内存的数据,取决于其他尚未完成的指令,等等),那么它会被延迟一些周期。在此期间处理器执行一些新指令,这些指令位于我们代码中的延迟指令之后,因为它们不以任何方式依赖于延迟指令。
现在我们可以看到latency的问题了。即使一条指令被解码并且它的所有输入都已经可用,也需要几个周期才能完全执行。这种延迟称为指令延迟。但是,我们知道此时处理器可以执行许多其他独立指令,如果有的话。
如果一条指令从二级缓存中加载数据,则需要等待大约10个周期才能加载数据。如果数据仅位于 RAM 中,则需要数百个周期才能将其加载到处理器。在这种情况下,我们可以说该指令具有高延迟。此时执行其他一些独立操作对于获得最佳性能非常重要。这有时称为 延迟隐藏。
最后,我们不得不承认,大多数实际代码本质上都是顺序的。它有一些独立的指令可以并行执行,但不会太多。没有要执行的指令会导致 pipeline bubbles,并导致处理器晶体管的使用效率低下。另一方面,几乎在所有情况下,两个不同线程的指令都是自动独立的。这直接将我们引向了超线程的想法。
P.S. 您可能想阅读 Agner Fog's manual 以更好地理解现代 CPU 的内部结构。
超线程
当两个线程在单个内核上以超线程模式执行时,处理器可以交错执行它们的指令,从而允许用第二个线程的指令填充第一个线程的气泡。这允许更好地利用处理器的资源,特别是在普通程序的情况下。请注意,HT 不仅在您有大量内存访问时有帮助,而且在大量顺序代码中也有帮助。优化良好的计算代码可能会充分利用 CPU 的所有资源,在这种情况下,您会看到 没有 从 HT 中获益(例如 dgemm
来自优化良好的例程BLAS).
P.S. 您可能想阅读英特尔的 detailed explanation of hyper-threading,包括有关哪些资源被复制或共享的信息,以及有关性能的讨论。
上下文切换
context是CPU的一个内部状态,至少包括了所有的寄存器。当执行线程发生变化时,OS 必须进行上下文切换(详细说明here). According to this answer, context switch takes about 10 microseconds, while the time quant of scheduler is 10 milliseconds or more (see here)。所以上下文切换对总时间的影响不大,因为它们很少完成。请注意,在某些情况下,线程之间对 CPU 缓存的竞争会增加切换的有效成本。
然而,在超线程的情况下,每个核心在内部有两种状态:两组寄存器、共享缓存、一组执行单元。因此,当您 运行 4 个物理内核上的 8 个线程时,OS 无需进行任何上下文切换。当你在四核上 运行 16 个线程时,会执行上下文切换,但它们只占总时间的一小部分,如上所述。
流程管理员
说到您在流程管理器中看到的 CPU 利用率,它不衡量 CPU 管道的内部结构。 Windows 只能在线程 returns 执行到 OS 时通知:睡眠,等待互斥锁,等待 HDD,以及做其他缓慢的事情。结果,它认为如果有一个线程在其上工作,则该核心已被完全使用,该线程不会休眠或等待任何事情。例如,您可以检查 运行ning 无限循环 while (true) {}
是否会导致 CPU.
的充分利用
鉴于:
- 完全 CPU 绑定非常大(即超过几个 CPU 周期)作业,并且
- A CPU 具有 4 个物理内核和总共 8 个逻辑内核,
8、16 和 28 个线程的性能是否可能优于 4 个线程?我的理解是 4 个线程执行的上下文切换 较少 并且在任何意义上都会 较少的开销 8, 16或者 28 个线程将在 4 个物理核心机器上 。但是,时间是 -
Threads Time Taken (in seconds)
4 78.82
8 48.58
16 51.35
28 52.10
下面的原始问题部分提到了用于测试获取时间的代码。 CPU规格也在底部给出。
看完各位网友的回答和评论里给出的信息,我终于可以把问题归结为我上面写的。如果上面的问题给了你完整的上下文,你可以跳过下面的原始问题。
原问题
我们说
是什么意思Hyper-threading works by duplicating certain sections of the processor—those that store the architectural state—but not duplicating the main execution resources. This allows a hyper-threading processor to appear as the usual "physical" processor and an extra "logical" processor to the host operating system
?
This question 今天在 SO 上被问到,它基本上测试了执行相同工作的多个线程的性能。它有以下代码:
private static void Main(string[] args)
{
int threadCount;
if (args == null || args.Length < 1 || !int.TryParse(args[0], out threadCount))
threadCount = Environment.ProcessorCount;
int load;
if (args == null || args.Length < 2 || !int.TryParse(args[1], out load))
load = 1;
Console.WriteLine("ThreadCount:{0} Load:{1}", threadCount, load);
List<Thread> threads = new List<Thread>();
for (int i = 0; i < threadCount; i++)
{
int i1 = i;
threads.Add(new Thread(() => DoWork(i1, threadCount, load)));
}
var timer = Stopwatch.StartNew();
foreach (var thread in threads) thread.Start();
foreach (var thread in threads) thread.Join();
timer.Stop();
Console.WriteLine("Time:{0} seconds", timer.ElapsedMilliseconds/1000.0);
}
static void DoWork(int seed, int threadCount, int load)
{
var mtx = new double[3,3];
for (var i = 0; i < ((10000000 * load)/threadCount); i++)
{
mtx = new double[3,3];
for (int k = 0; k < 3; k++)
for (int l = 0; l < 3; l++)
mtx[k, l] = Math.Sin(j + (k*3) + l + seed);
}
}
(为了便于阅读,我剪掉了几个大括号将代码放在一个页面中。)
我 运行 我的机器上的这段代码用于复制问题。我的机器有 4 个物理内核和 8 个逻辑内核。上面代码中的方法 DoWork()
是完全 CPU 绑定的。 我觉得超线程可能有助于提高 30% 的速度(因为这里我们有与物理内核一样多的 CPU 绑定线程(即 4))。但它几乎确实获得了 64% 的性能增益。 当我 运行 这段代码用于 4 个线程时,它花费了大约 82 秒,而当我 运行 这段代码用于 8、16 和 28 个线程时,它 运行 在所有情况下大约 50 秒。
时间总结:
Threads Time Taken (in seconds)
4 78.82
8 48.58
16 51.35
28 52.10
我可以看到 4 个线程的 CPU 使用率约为 50%。 不应该是 ~100% 吗? 毕竟我的处理器只有 4 个物理内核。 8 个和 16 个线程的 CPU 使用率约为 100%。
如果有人能在开头解释引用的文字,我希望能更好地理解 超线程,进而希望得到 为什么会这样的答案完全 CPU 绑定的进程与超线程一起工作得更好?。
为了完成,
- 我有英特尔酷睿 i7-4770 CPU @ 3.40 GHz,3401 MHz,4 个核心,8 个逻辑处理器。
- 我运行发布模式下的代码。
- 我知道测量计时的方式很糟糕。这只会给出最慢线程的时间。我从另一个问题中获取了代码。但是,当 运行 4 CPU 绑定线程在 4 物理核心机器上时,50% CPU 使用的理由是什么?
I could see that CPU usage was ~50% with 4 threads. Shouldn't it be ~100%?
不,不应该。
what is the justification for 50% CPU usage when running 4 CPU bound threads on a 4 physical core machine?
这就是 CPU 利用率在 Windows 中的报告方式(顺便说一句,至少在其他一些 OS 中也是如此)。 HT CPU 在操作系统中显示为两个内核,并按此报告。
因此,当您有四个 HT CPU 时,Windows 会看到一个八核机器。如果您查看任务管理器中的 "Performance" 选项卡,您将看到八个不同的 CPU 图表,并且计算总 CPU 利用率时,100% 的利用率是这八个核心的全部利用率.
如果您只使用四个线程,那么这些线程无法充分利用可用的 CPU 资源 ,这就解释了计时 。他们最多可以使用八个可用内核中的四个,因此您的利用率当然会达到 50%。 一旦超过逻辑核心数 (8),运行时间将再次增加;在这种情况下,您正在添加调度开销而不添加任何新的计算资源。
顺便说一下……
HyperThreading 与过去的共享缓存和其他限制相比有了很大改进,但它仍然无法提供与完整 CPU 相同的吞吐量优势,因为 CPU 中仍然存在一些争论=32=]。因此,即使忽略 OS 开销,你 35% 的速度提升对我来说也很不错。我经常看到将额外的 HT 核心添加到计算瓶颈的过程中,速度提升不超过 20%。
超线程通过在处理器执行管道中交错指令来工作。当处理器在一个 'thread' 上执行读写操作时,它正在对另一个 'thread' 执行逻辑评估,将它们分开并让您感觉性能翻倍。
你获得如此大加速的原因是因为你的 DoWork
方法中没有分支逻辑。这都是一个大循环,执行顺序非常可预测。
处理器执行管道必须经过几个时钟周期才能执行单个计算。处理器尝试通过使用接下来的几条指令预加载执行缓冲区来优化性能。如果加载的指令实际上是条件跳转(例如 if
语句),这是个坏消息,因为处理器必须刷新整个流水线并从内存的不同部分获取指令。
您可能会发现,如果将 if
语句放入 DoWork
方法中,您将无法获得 100% 的加速...
我无法解释您观察到的绝对加速量:100% 似乎对超线程的改进太大了。但是我可以把原理解释到位。
超线程的主要好处是当处理器必须在线程之间切换时。每当线程数多于 CPU 个内核时(99.9997% 的时间是正确的)并且 OS 决定切换到不同的线程,它必须执行(大部分)以下步骤:
- 保存当前线程的状态:这包括堆栈、寄存器的状态和程序计数器。它们保存的位置取决于体系结构,但一般来说,它们要么保存在缓存中,要么保存在内存中。无论哪种方式,这一步都需要时间。
- 将线程置于 "Ready" 状态(与 "Running" 状态相反)。
- 加载下一个线程的状态:同样,包括堆栈、寄存器和程序计数器,这又是一个步骤,需要时间。
- 将线程翻转到 "Running" 状态。
在普通(非HT)CPU中,它拥有的核心数就是处理单元的数量。其中每一个都包含寄存器、程序计数器(寄存器)、堆栈计数器(寄存器)、(通常)单独的高速缓存和完整的处理单元。所以如果一个普通的 CPU 有 4 个核心,它可以同时 运行 4 个线程。当一个线程完成时(或者 OS 认为它花费了太多时间并且需要等待轮到它再次启动),CPU 需要遵循这四个步骤来卸载线程并加载在新的执行之前可以开始新的。
另一方面,在超线程CPU中,上述情况成立,但除此之外,每个内核都有一组重复的寄存器、程序计数器、堆栈计数器和(有时)缓存。这意味着 4 核 CPU 仍然只能同时拥有 4 个线程 运行ning,但是 CPU 可以同时拥有 "preloaded" 个线程重复的寄存器。所以 4 个线程是 运行ning,但是 8 个线程被加载到 CPU,4 个活跃,4 个不活跃。然后,当 CPU 需要切换线程时,不必在线程需要切换时执行 loading/unloading,只需 "toggles" 哪个线程处于活动状态,并且在新 "inactive" 寄存器的后台执行 unloading/loading。还记得我用 "these steps take time" 后缀的两个步骤吗?在超线程系统中,步骤 2 和 4 是唯一需要实时执行的步骤,而步骤 1 和 3 在硬件后台执行(脱离任何线程或进程的概念或 CPU 核心)。
现在,这个过程并不能完全加速多线程软件,但在线程经常执行非常小的工作负载的环境中,线程切换的数量可能会很昂贵。即使在不符合该范例的环境中,也可以从超线程中获益。
如果您需要任何说明,请告诉我。 CS250 已经有几年了,所以我可能会在这里或那里混淆术语;如果我对某事使用了错误的术语,请告诉我。我 99.9997% 确定我所描述的一切在其运作逻辑方面都是准确的。
CPU管道
每条指令都必须经过pipeline中的几个步骤才能完全执行。至少,它必须被解码,发送到执行单元,然后在那里实际执行。现代CPUs上有几个执行单元,它们可以完全并行地执行指令。顺便说一句,执行单元不可互换:某些操作只能在单个执行单元上完成。例如,内存加载通常专门针对一个或两个单元,内存存储专门发送给另一个单元,所有计算都由其他单元完成。
了解流水线,我们可能会想:如果我们编写纯顺序代码并且每条指令都必须经过这么多流水线阶段,CPU 怎么能工作得这么快?答案如下:处理器以 out-of-order 方式执行指令。它有一个很大的重新排序缓冲区(例如,用于 200 条指令),并通过其管道并行推送许多指令。如果在任何时候某些指令由于任何原因无法执行(等待来自慢速内存的数据,取决于其他尚未完成的指令,等等),那么它会被延迟一些周期。在此期间处理器执行一些新指令,这些指令位于我们代码中的延迟指令之后,因为它们不以任何方式依赖于延迟指令。
现在我们可以看到latency的问题了。即使一条指令被解码并且它的所有输入都已经可用,也需要几个周期才能完全执行。这种延迟称为指令延迟。但是,我们知道此时处理器可以执行许多其他独立指令,如果有的话。
如果一条指令从二级缓存中加载数据,则需要等待大约10个周期才能加载数据。如果数据仅位于 RAM 中,则需要数百个周期才能将其加载到处理器。在这种情况下,我们可以说该指令具有高延迟。此时执行其他一些独立操作对于获得最佳性能非常重要。这有时称为 延迟隐藏。
最后,我们不得不承认,大多数实际代码本质上都是顺序的。它有一些独立的指令可以并行执行,但不会太多。没有要执行的指令会导致 pipeline bubbles,并导致处理器晶体管的使用效率低下。另一方面,几乎在所有情况下,两个不同线程的指令都是自动独立的。这直接将我们引向了超线程的想法。
P.S. 您可能想阅读 Agner Fog's manual 以更好地理解现代 CPU 的内部结构。
超线程
当两个线程在单个内核上以超线程模式执行时,处理器可以交错执行它们的指令,从而允许用第二个线程的指令填充第一个线程的气泡。这允许更好地利用处理器的资源,特别是在普通程序的情况下。请注意,HT 不仅在您有大量内存访问时有帮助,而且在大量顺序代码中也有帮助。优化良好的计算代码可能会充分利用 CPU 的所有资源,在这种情况下,您会看到 没有 从 HT 中获益(例如 dgemm
来自优化良好的例程BLAS).
P.S. 您可能想阅读英特尔的 detailed explanation of hyper-threading,包括有关哪些资源被复制或共享的信息,以及有关性能的讨论。
上下文切换
context是CPU的一个内部状态,至少包括了所有的寄存器。当执行线程发生变化时,OS 必须进行上下文切换(详细说明here). According to this answer, context switch takes about 10 microseconds, while the time quant of scheduler is 10 milliseconds or more (see here)。所以上下文切换对总时间的影响不大,因为它们很少完成。请注意,在某些情况下,线程之间对 CPU 缓存的竞争会增加切换的有效成本。
然而,在超线程的情况下,每个核心在内部有两种状态:两组寄存器、共享缓存、一组执行单元。因此,当您 运行 4 个物理内核上的 8 个线程时,OS 无需进行任何上下文切换。当你在四核上 运行 16 个线程时,会执行上下文切换,但它们只占总时间的一小部分,如上所述。
流程管理员
说到您在流程管理器中看到的 CPU 利用率,它不衡量 CPU 管道的内部结构。 Windows 只能在线程 returns 执行到 OS 时通知:睡眠,等待互斥锁,等待 HDD,以及做其他缓慢的事情。结果,它认为如果有一个线程在其上工作,则该核心已被完全使用,该线程不会休眠或等待任何事情。例如,您可以检查 运行ning 无限循环 while (true) {}
是否会导致 CPU.