【问题标题】:Does M:N threading model really utilizes CPU cores?M:N 线程模型真的使用 CPU 内核吗?
【发布时间】:2019-12-01 23:16:06
【问题描述】:

有几种线程模型可用于在应用程序中调度线程:

  • 1:1(内核级线程):用户创建的每个线程都映射到内核中的调度线程。
  • N:1(用户级线程):用户在单个应用程序中创建的所有线程实际上都映射到单个调度的内核线程。
  • M:N(混合线程):用户在应用程序中创建的 M 个线程被映射到 N 个内核线程。

用户级线程被认为比内核级线程更快,因为内核级的上下文切换比用户级更昂贵。用户级线程的一大缺点是它们不使用多处理器系统,因为它们只使用一个内核级线程。

有些文章说 M:N 线程模型最好使用 N 作为 CPU 内核数(here 就是一个例子)。这样我们就可以同时发挥 1:1 和 N:1 线程模型的优势。

我的问题是:

  1. 当我们使用内核级线程时,我们还会在执行期间获得“额外的”时间片(与用户级线程相反),所以它不能弥补缓慢的上下文切换吗?
  2. 为什么 CPU 内核的数量在这里甚至是相关的?据我了解,这里的 CPU 内核数量是非常透明的,因为即使我们使用确切数量的内核线程,也无法保证它们确实是同时执行的,因为其他内核可以执行来自其他进程的其他线程,并且“我们的' 线程之后可能仍会使用上下文切换。因此,无论我们拥有多少 CPU 内核,他们都使用上下文切换。我错了吗?。

【问题讨论】:

    标签: multithreading multicore


    【解决方案1】:

    用户级线程被认为比内核级线程快

    两者都可以更快,具体取决于工作负载、操作系统、硬件和绿色线程实现。

    这不是弥补了缓慢的上下文切换吗?

    有时。通常没有。内核线程有一个堆栈,当您拥有数千个它们时,它们会消耗千兆字节的 RAM,并且当它们进行上下文切换时,您可以保证有大量的缓存未命中。这是假设您的工作负载 IO 繁重,并且您经常进行上下文切换。

    为什么 CPU 内核的数量在这里甚至是相关的?

    无关紧要。您应该使用硬件线程数,许多现代 CPU 每个内核有 2 个硬件线程。

    其他内核可以执行来自其他进程的其他线程

    如果它们需要相当长的时间,则意味着您有 2 个进程加载系统。在这种情况下,更好的方法可能是使用 50% 的硬件线程。当人们设计需要资源的软件时,他们通常认为这将是计算机的主要工作负载。

    创建一个新的内核级线程(在具有单个硬件线程的系统上)通常不会因为上下文切换而给我额外的 CPU 时间

    如果还有其他进程也需要 100% CPU,那么使用 2 个线程确实可以获得额外的 CPU 时间。但这是罕见的边缘情况,由于系统无响应,用户会点击重置按钮。通常,除非涉及阻塞 IO 或安全性,否则创建比硬件线程更多的内核线程几乎没有意义。

    【讨论】:

    • 因此,如果我正确理解您的第二个答案,创建一个新的内核级线程(在具有单个硬件线程的系统上)通常不会因为上下文切换而给我额外的 CPU 时间?新线程的唯一“真正”好处是当我使用阻塞操作时?
    • 另外,您的第四个答案是指资源需求软件,但资源需求软件是使用大量资源的软件,因此如果它仅使用与硬件线程一样多的线程,则不是真的很需要资源..
    • @YAYAdest 1 查看更新 2 CPU 时间只是一种资源。此外,理论上,当您只运行一个进程时,由于零上下文切换,拥有 kernel=hardware 线程可以为您提供最佳性能。创建更多内核线程会降低性能,因为您会引入上下文切换、相关的缓存未命中、调度程序开销。
    • 好的,我现在明白了,由于某种原因,我忘记了 CPU 通常不是满负荷运行......所以虽然 CPU 不是 100%,但创建另一个线程是没有意义的,因为它赢了不要真的给我们额外的时间片(因为额外的时间片无论如何都会由我们承担)。我说的对吗?
    • 您说“除非涉及阻塞 IO 或安全性,否则创建比硬件线程更多的内核线程没有什么意义”。并发性如何(例如在 Web 服务器中)?您的意思是在这种情况下最好使用用户线程,因为内核线程对它们没有优势吗?还是您的意思是根本不使用并发(出于某种原因)?什么是可能涉及的“安全”?
    【解决方案2】:

    Alan Cox 曾说过,在多核架构普及之前,他说:“计算机是一个状态机。线程是为不能编程状态机的人准备的。”

    在不同内核上调度的内核线程(至少有可能)是有意义的。根据我的经验,在绝大多数情况下,用户线程只不过是一种不必要的昂贵抽象,旨在让您无需明确考虑管理作为 CPU 核心的状态机。

    当然可以。我们并不总是,也许甚至通常不需要顶级性能。但是,如果您的方案不需要将最高性能作为首要考虑因素,我就不会担心线程模型,只需使用最简单的模型。如果您确实关心,我会使用 1:1 内核线程并处理单核多路复用明确的。

    【讨论】:

      【解决方案3】:

      Go(lang)? 就是一个很好的例子,它利用这个模型来实现并发性。一个 goroutine 可能从一个不同的内核线程中的 系统调用 返回,而不是从它分派的。 Go 旨在并声称既高效又实现高利用率。

      一个问题是线程(锤子)有许多应用程序(钉子状物体)。 并发编程是一种与系统运行特性相匹配的程序组织形式,与并行编程完全不同,后者旨在通过最大限度地利用资源来减少执行时间。有相当多的重叠,因为通常并发程序比它们的顺序等效程序更快且响应更快。

      (1):额外的时间片。可能有一些调度程序确实如此,但您所描述的是一个过载的系统——它需要做的工作比可用的资源多。过载可能是暂时的,但是为了使这成为一种设计选择,您将受制于在进程/作业/会话/之间平衡的调度程序升级?而不是线程;你的 extra 是一个实现细节。

      (2): 是的,你在这里是对的多于错的。仅创建 N 个内核线程是不够的,除非您的机器具有某种形式的协同调度,但仍然可能会受到需要在真实 io 上同步的系统调用的影响(例如。read(2))。冒着成为 Go-fanboi 的风险,Go 调度程序通过在 #execution 单元之外保留停放内核线程的系统调用 slush 资金来规避这一点;所以真的有一个 L:M:N 线程模型。

      【讨论】:

      • 所以如果我理解正确的话,我们需要多线程的常见情况有 3 种。 1 用于阻塞 IO,在这种情况下我们应该只使用内核线程。 2 是为了并行性(运行得更快),在这种情况下,我们将使用内核线程,直到我们拥有的逻辑核心数量,因为超过这个数量并不会真正给我们更多的 CPU 时间(通常)。 3 用于并发,在这种情况下,我们需要 M:N 模型(其中 N=逻辑内核)以实现更快的执行以及比内核更多的线程。对吗?
      • L:M:N 线程模型是什么意思?我以为 N 是内核线程,M 是 goroutines,但是 L 是什么?
      • 我可能不应该发明新的符号,但是 goroutines 是一个 L,因为 go 调度程序在 N 个虚拟 CPU 上维护 M 个实际线程。因此,当 Go 设置为使用 4 个内核时,它会启动 7 个线程以适应内核调用中的一些阻塞,并将它们多路复用到 L 个 go-routines 中。这是一个更细微的调度系统的非常粗略的近似。
      猜你喜欢
      • 1970-01-01
      • 2012-09-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-09
      相关资源
      最近更新 更多