抢占式线程与非抢占式线程

71

请有人解释一下抢占式线程模型和非抢占式线程模型的区别:

根据我的理解:

  • 非抢占式线程模型:线程一旦启动,就不能被停止或转移控制权到其他线程,直到线程完成其任务。
  • 抢占式线程模型:运行时允许在任何时候从一个线程中接管控制权并将其交给另一个线程。高优先级线程优先于低优先级线程。

请有人:

  1. 解释理解是否正确。
  2. 解释两种模型的优缺点。
  3. 举例说明何时使用哪个模型会非常有帮助。
  4. 如果我在Linux中创建一个线程(System V或Pthread),没有指定任何选项(是否有?)默认使用的是抢占式线程模型吗?
1. 理解正确。 2. 非抢占式模型易于理解和实现,但是可能导致长时间等待某些线程完成任务而无法执行其他操作,可能导致资源浪费。抢占式模型可以更好地利用系统资源,但是需要更复杂的实现,可能会导致性能下降。 3. 如果需要简单的线程管理并且任务执行时间比较短,则可以使用非抢占式模型。如果需要更好地利用系统资源或有长期运行的任务,则应使用抢占式模型。 4. 是的,默认情况下Linux使用抢占式线程模型。
4个回答

56
  1. 不,你的理解并不完全正确。非抢占式(也称为协作式)线程通常需要手动让出控制,以便其他线程在它们结束之前运行(尽管这是由该线程调用yield()(或其他类似函数)实现的。
  2. 抢占式线程更简单。协作式线程具有较少的开销。
  3. 通常使用抢占式。如果您发现您的设计具有大量的线程切换开销,则可以考虑使用协作式线程进行优化。在许多(大多数?)情况下,这将是一项投入相当大但收益微薄的工作。
  4. 是的,默认情况下会使用抢占式线程,不过如果您寻找CThreads软件包,它支持协作式线程。然而,如今很少有人需要协作式线程,我不确定该软件包是否已经更新了十年...

6
关于yield()的说明:不要在Linux上使用,因为它会导致性能极差。一个被yield的线程会被推到线程调度队列的最后,因此该线程只有在整个系统中的所有其他任务都执行完毕后才会被调度。 - Zan Lynx
在我看来,当主进程创建两个线程时,它们将并行执行。那么“非抢占式线程模型”是否使执行方式为(完成线程1)->(完成线程2)-> main()?我的意思是,在线程1完全完成后,线程2将开始运行,然后在完成后,将调用main()方法。如果是这样的话,“非抢占式线程”有什么用呢? - rakeshNS
1
抢占式线程对于开发人员来说确实更加复杂,但也许更容易实现?我很难理解你所说的“抢占式更简单”的意思是什么? - Alexander Mills
1
@AlexanderMills:我的意思是说抢占式线程更容易使用。我仍然坚持这个观点。例如,微软在Windows NT 3.51中添加了纤程。它们主要是为内部使用(如SQL Server)添加的,而且他们早就建议不要将其作为一般规则使用。虽然有一些设计(例如Goroutines)并不太糟糕,但与完全抢占式线程相比,它们仍然存在相当大的问题。 - Jerry Coffin
有趣的是,尽管根据所有答案来看,协作线程并不像抢占式线程那样受欢迎。但在 Kotlin 中,协程正在使用该概念。协程可以通过 yielddelay 或其他挂起函数暂停,使协程“合作”,以便其他协程可以在同一线程中运行。 - r0n9
显示剩余2条评论

31

非抢占式线程也称为协作式线程。其中一个例子是POE (Perl)。另一个例子是经典的Mac OS(在OS X之前)。协作式线程独占CPU直到它们放弃。然后,调度程序选择另一个要运行的线程。

抢占式线程可以像协作式线程一样自愿放弃CPU,但当它们不这样做时,CPU会被从它们那里取走,并且调度程序将启动另一个线程。 POSIX和SysV线程属于这一类别。

协作式线程的重大优势是更高的效率(至少在单核机器上)和更容易处理并发性:只有在您放弃控制时才存在,因此不需要锁定。

抢占式线程的重大优势是更好的容错性:单个线程无法让所有其他线程停止执行。此外,通常在多核机器上工作得更好,因为多个线程同时执行。最后,您不必担心确保不断地放弃控制。例如,在重型数值计算循环中进行此操作可能非常麻烦。

当然可以混合使用它们。单个抢占式线程可以在内部运行多个协作式线程。


3
我已经回滚了你的编辑,"存在(Exists)"是本意——仅当你通过yielding显式地允许另一个线程运行时,才会存在并发(多个线程同时运行)。"退出(Exits)"没有意义。我也不确定你为什么将"isn't"改为"is not"。 - derobert
2
@derobet 没问题。那是一个看起来有道理的建议编辑,但由于建议中的一个打字错误,我重新编辑了它。当时我将单词“yield”与“exit”而不是“exists”联系在一起。老实说,正是打字错误“isn't -> is'n not”(或类似的错误)导致我接受并编辑了该建议。我很抱歉我的正确拼写追求导致我搞砸了你的答案。 - johnc

9
如果您使用非抢占式模型,这并不意味着进程在等待I/O时不执行上下文切换。调度程序将根据调度模型选择另一个进程。我们必须信任该进程。 非抢占式:
  1. 较少的上下文切换,在非抢占式模型中可以减少开销

  2. 更易于管理,因为它可以使用单核处理器来处理

抢占式:

优点:

  1. 在该模型中,我们有一个优先级,这有助于我们更好地控制正在运行的进程

  2. 更好的并发性是一个优点

  3. 处理系统调用而不会阻塞整个系统

缺点:

  1. 需要更复杂的同步算法,且无法避免关键部分处理。

  2. 伴随而来的开销。


1
合作(非抢占式)模型中,一旦线程获得控制权,它会一直运行,直到显式地放弃控制权或者被阻塞。
抢占式模型中,虚拟机可以随时介入并将控制权从一个线程移交给另一个线程。两种模型都有各自的优缺点。
Java线程通常是优先级抢占的。高优先级线程优先于低优先级线程。如果高优先级线程进入睡眠或者阻塞状态,那么低优先级线程可以运行(假设有一个可用的且准备好运行的线程)。
然而,一旦高优先级线程醒来或解除阻塞,它就会打断低优先级线程并运行直到完成、再次阻塞或被甚至更高优先级的线程抢占。
Java语言规范偶尔允许虚拟机运行低优先级线程而不是可运行的高优先级线程,但实际上这种情况很少发生。
然而,Java语言规范中没有指定相同优先级线程应该发生什么。在某些系统中,这些线程将被分时调度,运行时会分配一定的时间给一个线程。当时间到期时,运行时会抢占正在运行的线程并切换到具有相同优先级的下一个线程。

在其他系统中,一个正在运行的线程不会被一个优先级相同的线程抢占。它将继续运行,直到被阻塞、显式地放弃控制或被高优先级线程抢占。

至于优点,derobert和pooria都已经非常清楚地突出了它们。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接