【发布时间】:2010-12-28 10:17:34
【问题描述】:
“协程”和“线程”有什么区别?
【问题讨论】:
标签: architecture multithreading system coroutine
“协程”和“线程”有什么区别?
【问题讨论】:
标签: architecture multithreading system coroutine
初读:Concurrency vs Parallelism - What is the difference?
并发是提供交错的任务分离 执行。并行性是同时执行多个 件工作以提高速度。 ——https://github.com/servo/servo/wiki/Design
简答: 对于线程,操作系统根据其调度程序抢先切换正在运行的线程,这是操作系统内核中的一种算法。使用协程,程序员和编程语言决定何时切换协程;换句话说,任务是通过在设定点暂停和恢复功能来协同处理多任务的,通常(但不一定)在单个线程中。
长答案: 与由操作系统抢先调度的线程相比,协程切换是协作的,这意味着程序员(可能还有编程语言及其运行时)控制会发生切换。
与抢占式线程相比,协程切换是 合作(程序员控制何时发生切换)。这 内核不参与协程切换。 ——http://www.boost.org/doc/libs/1_55_0/libs/coroutine/doc/html/coroutine/overview.html
支持本机线程的语言可以在操作系统线程(内核线程)上执行其线程(用户线程)。每个进程至少有一个内核线程。内核线程与进程类似,不同之处在于它们在自己的进程中与该进程中的所有其他线程共享内存空间。进程“拥有”所有分配给它的资源,如内存、文件句柄、套接字、设备句柄等,并且这些资源都在其内核线程之间共享。
操作系统调度程序是内核的一部分,它运行每个线程一段时间(在单处理器机器上)。调度程序为每个线程分配时间(时间片),如果线程在这段时间内没有完成,调度程序会抢占它(中断它并切换到另一个线程)。多个线程可以在多处理器机器上并行运行,因为每个线程都可以(但不一定必须)调度到单独的处理器上。
在单处理器机器上,线程被时间片和抢占(切换)快速(在 Linux 上,默认时间片为 100 毫秒),这使得它们可以并发。但是,它们不能并行(同时)运行,因为单核处理器一次只能运行一件事。
协程和/或生成器可用于实现协作功能。它们不是在内核线程上运行并由操作系统调度,而是在单个线程中运行,直到它们让出或完成,让出给程序员确定的其他函数。具有生成器的语言,例如 Python 和 ECMAScript 6,可用于构建协程。 Async/await(见于 C#、Python、ECMAscript 7、Rust)是一种建立在生成器函数之上的抽象,可以产生期货/承诺。
在某些情况下,协程可能指的是堆栈函数,而生成器可能指的是无堆栈函数。
Fibers、轻量级线程和绿色线程是协程或类似协程的事物的其他名称。它们有时看起来(通常是故意的)更像编程语言中的操作系统线程,但它们不像真正的线程那样并行运行,而是像协程一样工作。 (根据语言或实现的不同,这些概念之间可能存在更具体的技术细节或差异。)
例如,Java 有“绿色线程”;这些线程是由 Java 虚拟机 (JVM) 调度的,而不是在底层操作系统的内核线程上本机调度的。这些没有并行运行或利用多个处理器/内核——因为这需要一个本机线程!由于它们不是由操作系统调度的,它们更像是协程而不是内核线程。在 Java 1.2 引入本机线程之前,Java 一直使用绿色线程。
线程消耗资源。在 JVM 中,每个线程都有自己的堆栈,通常大小为 1MB。 64k 是 JVM 中每个线程允许的最小堆栈空间量。可以在命令行上为 JVM 配置线程堆栈大小。尽管有这个名字,但线程并不是免费的,因为它们使用资源,例如每个线程需要自己的堆栈、线程本地存储(如果有的话)以及线程调度/上下文切换/CPU 缓存失效的成本。这就是协程在性能关键、高并发的应用程序中变得流行的部分原因。
Mac OS 只允许一个进程分配大约 2000 个线程,而 Linux 为每个线程分配 8MB 堆栈,并且只允许物理 RAM 中容纳的线程数。
因此,线程是最重的(就内存使用和上下文切换时间而言),然后是协程,最后是生成器是最轻的。
【讨论】:
晚了大约 7 年,但这里的答案缺少一些关于协同例程与线程的上下文。为什么协程最近受到如此多的关注,与线程相比,我什么时候会使用它们?
首先,如果协同程序并发运行(从不并行),为什么会有人更喜欢它们而不是线程?
答案是协程可以提供非常高水平的并发,而开销非常小。通常在线程环境中,在实际调度这些线程(由系统调度程序)浪费的开销数量之前,您最多有 30-50 个线程显着减少了线程实际执行有用工作的时间量。
好的,所以线程可以有并行性,但并行性不能太多,这不是比在单个线程中运行的协程更好吗?那么不一定。请记住,协程仍然可以在没有调度程序开销的情况下进行并发 - 它只是自己管理上下文切换。
例如,如果您有一个例程正在执行某些工作,并且它执行您知道会阻塞一段时间的操作(即网络请求),那么您可以使用协同例程立即切换到另一个例程,而无需包含这个决定中的系统调度程序 - 是的,程序员必须指定协同例程何时可以切换。
由于许多例程只做非常小的工作并自愿在彼此之间切换,您已经达到了调度程序无法达到的效率水平。您现在可以让数千个协程一起工作,而不是几十个线程。
因为您的例程现在在预定点之间相互切换,您现在还可以避免锁定共享数据结构(因为您永远不会告诉您的代码在中间切换到另一个协程关键部分)
另一个好处是内存使用率低得多。使用线程模型,每个线程都需要分配自己的堆栈,因此您的内存使用量随着您拥有的线程数线性增长。使用协同例程,您拥有的例程数量与您的内存使用量没有直接关系。
最后,协程受到了广泛关注,因为在某些编程语言(例如 Python)中,您的线程无论如何都不能并行运行 - 它们像协程一样并发运行,但没有低内存和空闲调度开销。
【讨论】:
协程是顺序处理的一种形式:在任何给定时间只有一个在执行(就像子程序 AKA 过程 AKA 函数一样——它们只是更流畅地相互传递接力棒)。
线程是(至少在概念上)并发处理的一种形式:多个线程可以在任何给定时间执行。 (传统上,在单 CPU、单核机器上,并发是在操作系统的帮助下模拟的——现在,由于很多机器都是多 CPU 和/或多核的,线程将事实上 同时执行,而不仅仅是“概念上”)。
【讨论】:
一句话:抢占。协程就像杂耍者一样,不断地向彼此传递经过精心排练的要点。线程(真正的线程)几乎可以在任何时候中断,然后再恢复。当然,这会带来各种资源冲突问题,因此 Python 臭名昭著的 GIL - Global Interpreter Lock。
许多线程实现实际上更像协程。
【讨论】:
讨论晚了 12 年,但协程的名称中有解释。 Coroutine 可以分解为 Co 和 Routine。
在这种情况下,例程只是一系列操作/动作,通过执行/处理例程,操作序列会按照指定的完全相同的顺序一个接一个地执行。
Co 代表合作。一个协同例程被要求(或更好地期望)自愿地暂停其执行,以给其他协同例程一个执行的机会。所以协程就是(自愿)共享 CPU 资源,以便其他人可以使用与自己相同的资源。
另一方面,线程不需要暂停其执行。被挂起对线程是完全透明的,并且线程被底层硬件强制挂起。它还以某种方式完成,因此它对线程几乎是透明的,因为它没有得到通知,并且它的状态没有改变,而是保存并稍后在允许线程继续时恢复。
有一件事是不正确的,协同例程不能同时执行,竞争条件也不能发生。这取决于运行协同程序的系统,并且很容易对协同程序进行成像。
协程如何挂起自己并不重要。早在 Windows 3.1 中,int 03 被编入任何程序(或必须放置在其中),而在 C# 中,我们添加了 yield。
【讨论】:
这取决于您使用的语言。比如 Lua 中的they are the same thing(协程的变量类型叫做thread)。
通常,尽管协同程序实现自愿让步,程序员决定在哪里yield,即将控制权交给另一个例程。
线程由操作系统自动管理(停止和启动),它们甚至可以在多核 CPU 上同时运行。
【讨论】: