有几种线程模型用于在应用程序中调度线程:
- 1:1(内核级线程):用户创建的每个线程都映射到内核中的一个调度线程
- N:1(用户级线程):用户在单个应用程序中创建的所有线程实际上都映射到单个调度的内核线程
- M:N(混合线程):用户在应用程序中创建的M个线程映射到N个内核线程
用户级线程被认为比内核级线程更快,因为内核级的上下文切换比用户级的代价更高。用户级线程的一大缺点是它们不使用多处理器系统,因为它们只使用一个内核级线程。
有一些文章告诉我们,M:N线程模型最好使用N作为CPU核心的数量(这里是一个例子)。通过这种方式,我们可以实现1:1和N:1线程模型的优势。
我的问题是:
- 当我们使用内核级线程时,我们在执行过程中也会得到"额外"的时间片(与用户级线程相反),所以这难道不能弥补上下文切换的缓慢吗
- 为什么CPU核心的数量在这里甚至是相关的?据我所知,CPU内核的数量在这里是非常透明的,因为即使我们使用确切数量的内核线程,也没有什么能保证它们真的同时执行,因为其他内核可以执行其他进程中的其他线程,而"我们的"线程之后可能仍然使用上下文切换。所以不管我们有多少CPU核心,他们都使用上下文切换。我错了吗
用户级线程被认为比内核级线程更快
根据工作负载、操作系统、硬件和绿色线程的实现,两者都可以更快。
这难道不能弥补上下文切换的缓慢吗?
有时。通常不会。内核线程有一个堆栈,当你有数千个内核线程时,它们会消耗千兆字节的RAM,当它们切换上下文时,你保证会有大量的缓存未命中。这是假设您的工作负载是IO繁重的,并且上下文切换频繁。
为什么CPU内核的数量在这里甚至是相关的?
无关。您应该使用多个硬件线程,许多现代CPU每个核心有2个硬件线程。
其他核心可以执行来自其他进程的其他线程
如果它们需要相当长的时间,则意味着您有两个进程加载系统。在这种情况下,更好的方法可能是使用50%的硬件线程。当人们设计需要资源的软件时,他们通常认为这将是计算机的主要工作负载。
创建一个新的内核级线程(在一个只有一个硬件线程的系统上)通常不会给我额外的CPU时间,因为上下文切换
如果有其他进程也需要100%的CPU,那么使用2个线程确实会获得额外的CPU时间。但这是一种罕见的边缘情况,用户会因为系统没有响应而按下重置按钮。一般来说,除非涉及阻塞IO或安全性,否则创建比硬件线程更多的内核线程没有什么意义。
Alan Cox曾说过,在多核体系结构普遍存在之前,"计算机是一台状态机。线程是为那些不能对状态机编程的人准备的。">
在不同的内核上调度内核线程是有意义的,至少可能是这样。根据我的经验,在绝大多数情况下,用户线程只不过是一种不必要的昂贵抽象,其设计目的是让您不必明确考虑管理作为CPU核心的状态机。
当然,这很好。我们并不总是,甚至可能通常,不需要顶级表现。但是,如果您的场景不需要将最高性能作为首要考虑因素,我就不必担心线程模型,只需使用最简单的模型。如果你真的关心的话,我会使用1:1内核线程,并显式地处理单核复用。
Go(lang)?,作为一个恰当的例子,它利用这个模型来实现并发性。goroutine可能会从系统调用返回,返回的内核线程与从中调度的内核线程不同。Go的目标和声称是既高效又能实现高度利用。
一个问题是螺纹(锤子)有很多应用(钉子状物体)并发编程是一种与系统运行特性相匹配的程序组织形式,与旨在通过最大限度地提高资源利用率来减少执行时间的并行程序截然不同。有相当大的重叠,因为通常并发程序比它们的顺序等价程序更快、响应更快。
(1) :额外的时间片。可能有一些调度器是这样的,但你所描述的是一个过载的系统——它有更多的工作要做,而不是可用的资源。过载可能是暂时的,但要将其作为一种设计选择,你将受制于在进程/作业/会话/??之间平衡的调度器升级???而不是线程;您的额外是一个实现细节。
(2) :是的,你在这里是对的多于错的。仅仅创建N个内核线程是不够的,除非您的机器具有某种形式的共调度,但仍然可能受到需要在实际io上同步的系统调用的影响(例如read(2))。冒着成为Go粉丝的风险,Go调度程序通过除了#执行单元之外,还保留一个由停放的内核线程组成的系统调用贿赂基金来规避这一风险;所以真的有一个L:M:N线程模型。