为什么OS线程被认为是昂贵的?

时间:2012-04-01 13:53:38

标签: multithreading threadpool

有许多解决方案适用于实现“用户空间”线程。无论是golang.org goroutines,python的绿色线程,C#的异步,erlang的进程等。这个想法是允许并发编程,即使只有一个或有限数量的线程。

我不明白的是,为什么操作系统线程如此昂贵?在我看来,无论哪种方式,你必须保存任务的堆栈(操作系统线程或用户空间线程),这是几十千字节,你需要一个调度程序在两个任务之间移动。

操作系统免费提供这两项功能。 OS线程为什么要比“绿色”线程更昂贵?由于为每个“任务”设置了专用的OS线程,导致性能下降的原因是什么?

6 个答案:

答案 0 :(得分:13)

我想修改都铎王朝的答案,这是一个很好的起点。线程有两个主要开销:

  1. 启动和停止它们。涉及创建堆栈和内核对象。涉及内核转换和全局内核锁。
  2. 保持堆叠。
  3. (1)只是一个问题,如果你一直在创建和停止它们。这通常使用线程池来解决。我认为这个问题实际上已经解决了。在线程池上调度任务通常不涉及到内核的访问,这使得它非常快。开销大约是几个互锁内存操作和一些分配。

    (2)只有当你有多个线程(> 100左右)时,这才会变得很重要。在这种情况下,异步IO是一种摆脱线程的方法。我发现,如果你没有疯狂的线程,同步IO包括阻塞比异步IO稍快(你读得正确:同步IO更快)。

答案 1 :(得分:6)

  

有许多解决方案旨在实现用户空间"线程。不管是golang.org goroutines,python的绿色线程,C#的异步,erlang的进程等。这个想法是允许并发编程,即使只有一个或有限数量的线程。

它是一个抽象层。对于许多人来说,掌握这个概念并在许多场景中更有效地使用它更容易。对于许多机器来说它也更容易(假设抽象很好),因为在许多情况下模型从宽度移动到拉伸。使用pthreads(作为示例),您拥有所有控件。对于其他线程模型,我们的想法是重用线程,创建并发任务的过程便宜,并使用完全不同的线程模型。消化这个模型要容易得多;学习和测量的人数较少,结果总体上很好。

  

我不明白的是,为什么操作系统线程如此昂贵?在我看来,无论哪种方式,你必须保存任务的堆栈(操作系统线程或用户空间线程),这是几十千字节,你需要一个调度程序在两个任务之间移动。

创建线程很昂贵,而堆栈需要内存。同样,如果您的进程使用多个线程,那么上下文切换可能会导致性能下降。因此,轻量级线程模型由于多种原因而变得有用。创建OS线程成为中型到大型任务的理想解决方案,理想情况是数量较少。这是限制性的,而且维护起来非常耗时。

任务/线程池/用户态线程不需要担心大部分上下文切换或线程创建。如果资源可用,它经常会重新使用资源,如果现在还没有准备就绪 - 同时,确定该机器的活动线程数"。

更常见的(IMO),操作系统级别的线程很昂贵,因为工程师没有正确使用它们 - 要么太多而且有大量的上下文切换,对同一组资源的竞争,任务太小了。理解如何正确使用OS线程以及如何将其最佳地应用于程序执行的上下文需要花费更多的时间。

  

操作系统免费提供这两项功能。

他们可以使用,但他们不是免费的。它们很复杂,对于良好的性能非常重要。当你创建一个操作系统线程时,它很快就会给出时间' - 所有过程'时间在线程之间划分。这不是用户线程的常见情况。当资源不可用时,任务通常会排队。这减少了上下文切换,内存和必须创建的线程总数。当任务退出时,线程被赋予另一个。

考虑时间分布的这种类比:

  • 假设你在赌场。有很多人想要卡片。
  • 您有固定数量的经销商。经销商比想要卡的人少。
  • 在任何时候,每个人都没有足够的卡片。
  • 人们需要所有牌来完成他们的比赛/手牌。当他们的比赛/手牌完成时,他们将牌返还给经销商。

您如何要求经销商分发卡片?

在OS调度程序下,这将基于(线程)优先级。每个人每次都会获得一张卡(CPU时间),并且会不断评估优先级。

人们代表任务或线程的工作。卡片代表时间和资源。经销商代表线程和资源。

如果有2个经销商和3个人,你会如何处理最快?如果有5个经销商和500个人?你怎么能最大限度地减少用完纸牌?使用线程,添加卡片和添加经销商不是您可以按需提供的解决方案。添加CPU相当于添加经销商。添加线程相当于经销商一次向更多人发牌(增加上下文切换)。有许多策略可以更快地处理卡片,特别是在您消除了人们在一定时间内对卡片的需求之后。如果经销商与人的比例为1/50,那么在他们的游戏完成之前去一张桌子并与一个人或人交易会不会更快?相比之下,根据优先级访问每个表,并协调所有经销商之间的访问(操作系统方法)。这并不意味着操作系统是愚蠢的 - 它意味着创建一个操作系统线程是一个工程师添加更多的人和更多的表,可能比经销商可以合理处理的更多。幸运的是,在许多情况下,可以通过使用其他多线程模型和更高的抽象来解除约束。

  

为什么OS线程要比" green"更昂贵?线程?由于为每个"任务提供专用的OS线程而导致性能下降的原因是什么?#/ p>

如果您开发了性能关键的低级别线程库(例如,在pthreads上),您将认识到重用的重要性(并在库中将其作为可供用户使用的模型实现)。从这个角度来看,更高级别多线程模型的重要性是基于现实世界使用的简单明了的解决方案/优化,以及可以降低采用和有效利用多线程的入门条的理想。

并不是说它们很贵 - 轻巧的线程'模型和池是许多问题的更好解决方案,对于不了解线程的工程师来说,更合适的抽象。在此模型下,多线程的复杂性大大简化(并且在实际使用中通常更具性能)。对于OS线程,您确实拥有更多控制权,但必须考虑更多因素以尽可能有效地使用它们 - 注意这些考虑因素可以极大地重新规划程序的执行/实现。通过更高级别的抽象,通过完全改变任务执行流程(宽度与拉动),可以最大限度地减少许多复杂性。

答案 2 :(得分:6)

保存堆栈是微不足道的,无论它的大小如何 - 堆栈指针需要保存在内核中的线程信息块中(因此通常可以保存大多数寄存器,因为它们将被任何软件推送/ hard interrupt导致操作系统被输入。

一个问题是需要保护级别的响铃周期才能从用户进入内核。这是一个必不可少但令人烦恼的开销。然后驱动程序或系统调用必须执行中断请求的任何操作,然后调度/调度线程到处理器上。如果这导致一个线程从另一个进程抢占一个线程,则还必须交换额外进程上下文的负载。如果操作系统决定运行在另一个处理器核心上的线程而不是处理中断mut的线程被抢占,则会增加更多的开销 - 另一个核心必须是硬件中断的(这是在硬/软中断之上)首先是操作系统。

因此,调度运行可能是一个非常复杂的操作。

'绿色线程'或'光纤'是(通常)从用户代码调度的。上下文更改比OS中断等更容易和更便宜,因为每次上下文更改都不需要Wagnerian响铃周期,进程上下文不会更改,并且运行绿色线程组的OS线程不会更改。

由于不存在任何东西,绿色线程存在问题。它们由“真正的”OS线程运行。这意味着,如果一个OS线程运行的组中的一个“绿色”线程阻止了OS调用,则该组中的所有绿色线程都将被阻止。这意味着像sleep()这样的简单调用必须由一个状态机“模拟”,这个状态机会产生其他绿色线程(是的,就像重新实现操作系统一样)。同样,任何线程间信令。

当然,绿色线程无法直接响应IO信号,因此在某种程度上无法解决首先出现任何线程的问题。

答案 3 :(得分:4)

为每个小任务启动内核线程的问题在于它会产生不可忽略的启动和停止开销,以及它所需的堆栈大小。

这是第一个重点:存在线程池,以便您可以回收线程,以避免浪费时间启动它们以及浪费堆栈的内存。

其次,如果您触发线程来执行异步I / O,它们将花费大部分时间来阻止等待I / O完成,从而有效地不做任何工作并浪费内存。一个更好的选择是让一个工作者处理多个异步调用(通过一些底层调度技术,例如多路复用),从而再次节省内存和时间。

使“绿色”线程比内核线程更快的一件事是它们是由虚拟机管理的用户空间对象。启动它们是一个用户空间调用,而启动一个线程是一个更慢的内核空间调用。

答案 4 :(得分:2)

A person in Google shows an interesting approach.

据他介绍,内核模式切换本身不是瓶颈,核心成本发生在SMP调度程序上。并且他声称M:由内核辅助的N计划并不昂贵,这使我期望在每种​​语言上都可以使用通用的M:N线程。

答案 5 :(得分:0)

我认为这两件事情处于不同的层面。

ThreadProcess是正在执行的程序的实例。在进程/线程中,还有更多的东西。执行堆栈,打开文件,信号,处理器状态以及许多其他内容。

Greentlet不同,它以vm运行。它提供轻质螺纹。其中许多提供伪并发(通常在单个或几个OS级别的线程中)。而且他们经常通过数据传输而不是数据共享来提供无锁方法。

所以,这两件事情的重点不同,所以重量不同。

在我看来,greenlet应该在虚拟机而不是操作系统中完成。