“协程”和“线程”之间的区别?


Answers:


122

协程是顺序处理的一种形式:在任何给定时间都只执行一个协程(就像子例程AKA过程AKA函数一样-它们只是使指挥棒之间的传递更加流畅)。

线程(至少在概念上)是并发处理的一种形式:多个线程可以在任何给定时间执行。(传统上,在单CPU单核计算机上,并发是在OS的帮助下进行模拟的。如今,由于许多计算机都是多CPU和/或多核的,因此线程实际上将同时执行,不只是“概念上”)。


188

首先阅读: 并发与并行-有什么区别?

并发是任务的分离,以提供交错执行。并行是为了提高速度而同时执行多个工作。- https://github.com/servo/servo/wiki/Design

简短的答案:对于线程,操作系统根据其调度程序抢先切换正在运行的线程,该调度程序是操作系统内核中的一种算法。对于协程,程序员和编程语言确定何时切换协程。换句话说,通过在设定点处暂停和恢复功能(通常(但非必须))在单个线程中,任务可以协同执行多任务。

长答案:与操作系统预先调度的线程相反,协程开关是协作的,这意味着程序员(可能还有编程语言及其运行时)控制何时进行切换。

与抢占式线程相比,协程开关是协作的(程序员控制何时进行开关)。协程开关中不涉及内核。- http://www.boost.org/doc/libs/1_55_0/libs/coroutine/doc/html/coroutine/overview.html

支持本机线程的语言可以在操作系统的线程(内核线程)上执行其线程(用户线程)。每个进程至少有一个内核线程。内核线程类似于进程,除了内核线程在其拥有的进程中与该进程中的所有其他线程共享内存空间外。进程“拥有”所有分配的资源,例如内存,文件句柄,套接字,设备句柄等,并且这些资源都在其内核线程之间共享。

操作系统调度程序是内核的一部分,内核在每个时间段(在单处理器计算机上)运行每个线程。调度程序为每个线程分配时间(时间片),如果该线程未在该时间内完成,调度程序将抢占它(中断并切换到另一个线程)。多个线程可以在多处理器计算机上并行运行,因为每个线程可以(但不一定必须)调度到单独的处理器上。

在单处理器计算机上,线程被快速分时和抢占(在它们之间切换)(在Linux上,默认时间片为100ms),这使它们并发。但是,它们不能并行(同时)运行,因为单核处理器一次只能运行一件事。

协程和/或生成器可用于实现协作功能。它们不是在内核线程上运行并由操作系统调度,而是在单个线程中运行,直到它们屈服或完成为止,并屈服于程序员确定的其他功能。带有生成器的语言(例如Python和ECMAScript 6)可用于构建协程。异步/等待(在C#,Python,ECMAscript 7,Rust中可见)是构建在生成期货/承诺的生成器函数之上的抽象。

在某些情况下,协程可能引用堆栈函数,而生成器可能引用无堆栈函数。

纤维轻量线程绿色线程是协程或类似协程的事物的别称。它们有时看起来(通常是有目的的)更像是编程语言中的操作系统线程,但是它们并不像真实线程一样并行运行,而是像协程那样并行运行。(取决于语言或实现,这些概念之间可能会有更具体的技术特性或差异。)

例如,Java有“ 绿色线程 ”。这些是由Java虚拟机(JVM)调度的线程,而不是本机在底层操作系统的内核线程上调度的线程。它们没有并行运行,也没有利用多个处理器/内核,因为这需要本机线程!由于它们不是由操作系统调度的,因此它们更像协程而不是内核线程。在将原生线程引入Java 1.2之前,绿色线程是Java使用的线程。

线程消耗资源。在JVM中,每个线程都有自己的堆栈,大小通常为1MB。64k是JVM中每个线程允许的最小堆栈空间量。可以在JVM的命令行上配置线程堆栈大小。尽管名称不同,但线程并不是免费的,因为它们的使用资源(如每个线程需要其自己的堆栈,线程本地存储(如果有))以及线程调度/上下文切换/ CPU缓存失效的成本。这是协程在性能关键的高并发应用程序中变得流行的部分原因。

Mac OS仅允许一个进程分配大约2000个线程,而Linux每个线程分配8MB的堆栈,并且只允许在物理RAM中容纳尽可能多的线程。

因此,线程是最重的权重(就内存使用和上下文切换时间而言),然后是协程,最后生成器是最轻的权重。


2
+1,但此答案可能会从一些参考文献中受益。
kojiro 2014年

1
绿色线程与协程有些不同。他们不是吗?甚至纤维也有一些差异。看到programmers.stackexchange.com/questions/254140/...

112

大约晚了7年,但是这里的答案缺少关于协程vs线程的上下文。为什么协程最近受到了如此多的关注?与线程相比,我何时使用它们?

首先,如果协程同时运行(绝不并行运行),为什么有人会喜欢协程而不是线程?

答案是,协程可以以很少的开销提供很高的并发性。通常,在线程环境中,在实际调度这些线程(由系统调度程序)浪费的开销量明显减少了线程实际执行有用工作的时间之前,您最多拥有30-50个线程。

好的,使用线程可以并行化,但是不要太多并行性,这还不比在单个线程中运行的协同例程好吗?那么不一定。请记住,协同例程仍可以在没有调度程序开销的情况下进行并发-它仅管理上下文切换本身。

例如,如果您有一个例程在做一些工作,并且执行了您知道会阻塞一段时间的操作(即网络请求),那么使用协同例程,您可以立即切换到另一个例程,而无需在系统例程中添加系统调度程序这个决定-是的,您必须由程序员指定协同例程何时可以切换。

由于许多例程只做很少的工作,并且彼此之间自愿切换,因此您已经达到了调度程序无法期望达到的效率水平。您现在可以使成千上万个协程协同工作,而不是数十个线程。

因为您的例程现在在彼此之间切换了一个预定点,所以您现在还可以避免锁定共享数据结构(因为您永远不会告诉您的代码在关键部分的中间切换到另一个协程)

另一个好处是内存使用率低得多。使用线程模型,每个线程都需要分配自己的堆栈,因此您的内存使用量会随着您拥有的线程数线性增长。使用协同例程,您拥有的例程数量与内存使用没有直接关系。

最后,协同例程引起了很多关注,因为在某些编程语言(例如Python)中,线程无论如何都不能并行运行-它们像协程一样并发运行,但是没有低内存和空闲的调度开销。


2
当遇到阻塞操作时,如何在协程中切换到另一个任务?
Narcisse Doudieu Siewe '16

切换到另一任务的方式是使任何阻塞操作实际上异步完成。这意味着您必须避免使用实际上会阻塞的任何操作,而只能使用在协程系统中使用时不阻塞的操作。解决此问题的唯一方法是让内核支持协程,例如Windows上的UMS,只要您的UMS“线程”在系统调用上阻塞,它就会跳入调度程序。
retep998 '16

@MartinKonecny最近的C ++ Threads TS是否遵循您提到的方法?
Nikos

因此,最终现代的编程语言将需要协程/光纤两者来有效地使用单个CPU内核来执行诸如IO和线程之类的非计算繁重的操作,以并行化许多内核上的CPU密集型操作以提高速度,对吗?
Mahatma_Fatal_Error

19

一言以蔽之:抢占。协程员的行为就像杂耍者一样,它们相互之间保持着良好的交集。线程(真线程)几乎可以在任何时候中断,然后在以后恢复。当然,这带来了各种各样的资源冲突问题,因此,Python臭名昭著的GIL-全局解释器锁。

实际上,许多线程实现更像协程。


9

这取决于您使用的语言。例如,在Lua中,它们是同一件事(协程的变量类型称为thread)。

通常,尽管协程在某些地方(您)决定了在何处(例如yield,将控制权交给另一个例程)实现了自愿的产量。

而是由操作系统自动管理(停止和启动)线程,它们甚至可以在多核CPU上同时运行。


0

讨论晚了12年,但协程名称中包含了解释。协程可以分解为Co和Routine。

在这种情况下,例程只是一系列操作/动作,通过执行/处理例程,可以按照指定的完全相同的顺序逐个执行一系列操作。

合作代表合作。一个协例程被要求(或更好地期望)中止其执行,以使其他协例程也有执行的机会。因此,一个协程是关于(自愿地)共享CPU资源,以便其他人可以使用与自己正在使用的资源相同的资源。

另一方面,线程不需要暂停其执行。挂起对线程是完全透明的,并且底层硬件强制线程将其自身挂起。它也以某种方式完成,以使它对线程几乎是透明的,因为它不会得到通知,并且其状态不会更改,而是保存并在允许线程继续执行时恢复。

不正确的一件事是,不能同时执行协例程,并且不会发生竞争条件。这取决于运行协同例程的系统,并且很容易对协同例程进行成像。

协同例程如何暂停自身并不重要。回到Windows 3.1 int 03,它被编织到任何程序中(或必须放在其中),在C#中,我们增加了yield。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.