如何在多核处理器中安排任务
How Tasks are scheduled in a multi-core processor
我对 多核处理器 中任务的调度方式感到困惑。其实,不同的消息来源有不同的看法。重要的是,关于多核处理器中的任务调度机制的文档还不够。所以,我决定问你一个问题。
我描述了一个包含进程内核线程和两个用户级线程的进程。并提供有关处理逻辑的伪代码。
问题是,这个过程将如何在包含 2 个物理内核和 4 个逻辑处理器(每个内核有 2 个)的多核处理单元中执行。这样,就没有任何等待进程,CPU 被完全分配给进程。
我猜它的工作原理如下:
注:PKT_C1_LP1表示进程内核线程分配给核心1和逻辑处理器1
|--PKT_C1_LP1--1s--| |--T1_C1_LP1--1s--| |--TSK1_C1_LP1--1s--|
|--T2_C1_LP2--2s-----------| |--TSK2_C1_LP2--1s--|
----------- timeline ----------->
更新
Seems like the answer(s) to your question(s) will depend a lot on what
OS and scheduler your system is running.
因为没有任何等待进程,也没有足够的资源。所以我相信almost中任何os中的所有调度算法都会有微不足道的差异。但是,为了简单起见,它是:
non-preemptive FCFS scheduling
这是每个线程需要执行的代码的时序图。这想象了一个最大的情况,其中每个任务立即产生一个新线程。绿色部分是无限短的代码片段(想想,“不按比例”),但基本上只是调度操作。而红色部分同样是短进程EXIT和线程END调度操作。 (我省略了与线程创建相关的惩罚。请注意工作线程不会结束,它们只是闲置,并且留在线程池中。
基本时序图
现在你会注意到的第一件事是,由于 tasks 的工作方式,第二个任务可以在调度它的同一个线程上执行,因为不再任务已安排好,线程只会等待该任务。这与线程调度无关,而与任务如何有效地管理其工作线程池有关。这是应用程序级代码,而不是完成此操作的 os 级代码。由于任务,下图需要少 1 个线程。
具有更智能任务的时序图
现在我们可以看看调度程序需要做什么。我们仍然只处理逻辑处理器。 (哪个核心将执行哪个线程的细节很复杂,所以让我们暂时不谈。)这里我们看到我们可以天真地在它们自己的处理器上执行所有这些线程。
贪婪地使用处理器
在以前的处理器之一上执行工作线程可能会更有效率。当 工作线程 1 需要执行时它们处于空闲状态,因此重用之前分配的处理器之一更有意义。 工作线程 1 中的 任务 1 代码显示在处理器 2 上执行(也可以分配给处理器 1,因为它也是空闲的,但是请继续关注下一张图,您会明白为什么我将它放在处理器 2 上)。
安排线程重用处理器
最后,我们可以构建将我们带到 most 高效调度的最后一个版本。这取决于优化创建线程然后立即加入线程的情况。不同的操作系统试图优化这种情况,使新创建的线程可以 运行 在同一个处理器上。这意味着创建线程不会立即在空闲处理器上调度新线程并将上下文切换的 cost 烧回调度它的线程。相反,新线程会在我们阻塞我们的 Join 操作时,或者在下一个时钟中断发生时被调度。如果我们可以在中断触发调度程序之前快速进入我们的 Join 调用(我们在典型的操作系统上讨论 < 10 ms 由时钟芯片触发此类事件)那么调度将像这样更有效地发生(下面),其中 thread 2 可以在同一处理器上调度到 运行 而无需上下文切换。 (有趣的是,Linux 和 Windows 对这种情况的优化不同。)
最终时序图
您会注意到(上图)这一切现在只能在 两个逻辑处理器 上执行。
在不同的内核或同一内核的不同逻辑处理器上运行它们是否更有效是操作系统的细微差别,这在很大程度上取决于虚拟内存的使用以及硬件规格处理器及其缓存。不同的操作系统也会在这里做不同的事情。细节非常重要。不统一的内存架构也会影响决策。
在现实世界中,操作系统可能会使用试探法来确定线程和进程的最佳优先级和位置。现实世界的答案与我给出的这个“计算机科学”答案有很大的不同和细微差别,并且取决于具体细节。
其他Reading/Viewing:
我对 多核处理器 中任务的调度方式感到困惑。其实,不同的消息来源有不同的看法。重要的是,关于多核处理器中的任务调度机制的文档还不够。所以,我决定问你一个问题。
我描述了一个包含进程内核线程和两个用户级线程的进程。并提供有关处理逻辑的伪代码。
问题是,这个过程将如何在包含 2 个物理内核和 4 个逻辑处理器(每个内核有 2 个)的多核处理单元中执行。这样,就没有任何等待进程,CPU 被完全分配给进程。
我猜它的工作原理如下:
注:PKT_C1_LP1表示进程内核线程分配给核心1和逻辑处理器1
|--PKT_C1_LP1--1s--| |--T1_C1_LP1--1s--| |--TSK1_C1_LP1--1s--|
|--T2_C1_LP2--2s-----------| |--TSK2_C1_LP2--1s--|
----------- timeline ----------->
更新
Seems like the answer(s) to your question(s) will depend a lot on what OS and scheduler your system is running.
因为没有任何等待进程,也没有足够的资源。所以我相信almost中任何os中的所有调度算法都会有微不足道的差异。但是,为了简单起见,它是:
non-preemptive FCFS scheduling
这是每个线程需要执行的代码的时序图。这想象了一个最大的情况,其中每个任务立即产生一个新线程。绿色部分是无限短的代码片段(想想,“不按比例”),但基本上只是调度操作。而红色部分同样是短进程EXIT和线程END调度操作。 (我省略了与线程创建相关的惩罚。请注意工作线程不会结束,它们只是闲置,并且留在线程池中。
基本时序图
现在你会注意到的第一件事是,由于 tasks 的工作方式,第二个任务可以在调度它的同一个线程上执行,因为不再任务已安排好,线程只会等待该任务。这与线程调度无关,而与任务如何有效地管理其工作线程池有关。这是应用程序级代码,而不是完成此操作的 os 级代码。由于任务,下图需要少 1 个线程。
具有更智能任务的时序图
现在我们可以看看调度程序需要做什么。我们仍然只处理逻辑处理器。 (哪个核心将执行哪个线程的细节很复杂,所以让我们暂时不谈。)这里我们看到我们可以天真地在它们自己的处理器上执行所有这些线程。
贪婪地使用处理器
在以前的处理器之一上执行工作线程可能会更有效率。当 工作线程 1 需要执行时它们处于空闲状态,因此重用之前分配的处理器之一更有意义。 工作线程 1 中的 任务 1 代码显示在处理器 2 上执行(也可以分配给处理器 1,因为它也是空闲的,但是请继续关注下一张图,您会明白为什么我将它放在处理器 2 上)。
安排线程重用处理器
最后,我们可以构建将我们带到 most 高效调度的最后一个版本。这取决于优化创建线程然后立即加入线程的情况。不同的操作系统试图优化这种情况,使新创建的线程可以 运行 在同一个处理器上。这意味着创建线程不会立即在空闲处理器上调度新线程并将上下文切换的 cost 烧回调度它的线程。相反,新线程会在我们阻塞我们的 Join 操作时,或者在下一个时钟中断发生时被调度。如果我们可以在中断触发调度程序之前快速进入我们的 Join 调用(我们在典型的操作系统上讨论 < 10 ms 由时钟芯片触发此类事件)那么调度将像这样更有效地发生(下面),其中 thread 2 可以在同一处理器上调度到 运行 而无需上下文切换。 (有趣的是,Linux 和 Windows 对这种情况的优化不同。)
最终时序图
您会注意到(上图)这一切现在只能在 两个逻辑处理器 上执行。
在不同的内核或同一内核的不同逻辑处理器上运行它们是否更有效是操作系统的细微差别,这在很大程度上取决于虚拟内存的使用以及硬件规格处理器及其缓存。不同的操作系统也会在这里做不同的事情。细节非常重要。不统一的内存架构也会影响决策。
在现实世界中,操作系统可能会使用试探法来确定线程和进程的最佳优先级和位置。现实世界的答案与我给出的这个“计算机科学”答案有很大的不同和细微差别,并且取决于具体细节。
其他Reading/Viewing: