什么是协程?它们与并发有什么关系?
13 回答
Coroutines and concurrency are largely orthogonal. Coroutines are a general control structure whereby flow control is cooperatively passed between two different routines without returning.
The 'yield' statement in Python is a good example. It creates a coroutine. When the 'yield ' is encountered the current state of the function is saved and control is returned to the calling function. The calling function can then transfer execution back to the yielding function and its state will be restored to the point where the 'yield' was encountered and execution will continue.
来自Lua中的“编程Coroutines
”部分:
协程类似于线程(在多线程的意义上):它是一行执行,有自己的堆栈、自己的局部变量和自己的指令指针;但它与其他协程共享全局变量和大部分其他内容。线程和协程之间的主要区别在于,在概念上(或字面意思,在多处理器机器中),具有线程的程序并行运行多个线程。另一方面,协程是协作的:在任何给定时间,具有协程的程序只运行它的一个协程,并且这个正在运行的协程只有在它明确请求暂停时才会暂停其执行。
所以重点是:协程是“协作的”。即使在多核系统中,任何时候也只有一个协程在运行(但多个线程可以并行运行)。协程之间是非抢占式的,运行中的协程必须显式放弃执行。
对于“ concurrency
”,您可以参考 Rob Pike 的幻灯片:
并发是独立执行计算的组合。
所以在协程A的执行过程中,它把控制权交给了协程B。一段时间后,协程B又把控制权交给了协程A。由于协程之间存在依赖关系,必须串联运行,所以两个协程不是并发的。
尽管这是一个技术问题,但我发现大多数答案都过于技术化。我很难理解协程过程。我有点明白,但我没有同时明白。
我发现这个答案非常有帮助:
https://dev.to/thibmaek/explain-coroutines-like-im-five-2d9
引用 Idan Arye 的话:
为了建立你的故事,我会这样说:
你开始看动画片,但它是介绍。不用看介绍,而是切换到游戏并进入在线大厅 - 但它需要 3 名玩家,而且只有你和你的妹妹在里面。无需等待其他玩家加入,而是切换到您的作业,并回答第一个问题。第二个问题有一个指向您需要观看的 YouTube 视频的链接。你打开它 - 它开始加载。您无需等待它加载,而是切换回卡通。介绍已经结束,大家可以观看。现在有广告 - 但同时第三位玩家加入了,所以你切换到游戏等等......
这个想法是,你不只是快速切换任务,让它看起来像你在一次做所有事情。您利用等待某事发生的时间(IO)来做其他需要您直接关注的事情。
一定要检查链接,还有更多我无法引用的内容。
协程类似于子程序/线程。不同之处在于,一旦调用者调用了子例程/线程,它就永远不会返回调用者函数。但是协程可以在执行几段代码后返回给调用方,允许调用方执行一些自己的代码并返回到协程停止执行的点并从那里继续。IE。协程有多个入口和出口点
- 协程是 Kotlin 语言中可用的强大功能
- 协程是一种编写异步、非阻塞代码(以及更多)的新方法
- 协程是轻量级线程。轻量级线程意味着它不会映射到本机线程,因此它不需要处理器上的上下文切换,因此它们更快。
- 它不映射到本机线程
- 协程和线程都是多任务处理。但不同之处在于线程由操作系统管理,协程由用户管理。
基本上,有两种类型的协程:
- 无堆叠
- 堆积如山
Kotlin 实现了无堆栈协程——这意味着协程没有自己的堆栈,因此它们不会映射到本机线程。
这些是启动协程的函数:
launch{}
async{}
您可以从这里了解更多信息:
https://www.kotlindevelopment.com/deep-dive-coroutines/
https://blog.mindorks.com/what-are-coroutines-in-kotlin-bf4fecd476e9
我发现此链接的解释非常简单。除了这个答案中的最后一个要点外,这些答案都没有试图解释并发性与并行性。
- 什么是并发(程序)?
引自传奇人物乔·阿姆斯特朗的“编程 Erlang”:
并发程序可以在并行计算机上运行得更快。
并发程序是用并发编程语言编写的程序。我们出于性能、可伸缩性或容错性的原因编写并发程序。
并发编程语言是一种具有用于编写并发程序的显式语言结构的语言。这些结构是编程语言的一个组成部分,并且在所有操作系统上的行为方式都相同。
并行计算机是具有多个可以同时运行的处理单元(CPU 或内核)的计算机。
所以并发与并行是不一样的。您仍然可以在单核计算机上编写并发程序。分时调度器会让您感觉您的程序正在同时运行。
并发程序有可能在并行计算机中并行运行,但不能保证。操作系统可能只给你一个内核来运行你的程序。
因此,并发是来自并发程序的软件模型,并不意味着您的程序可以在物理上并行运行。
- 协程和并发
“协程”一词由两个词组成:“co”(合作)和“routines”(函数)。
一个。它实现并发还是并行?
为简单起见,让我们在单核计算机上讨论它。
并发是通过操作系统的时间共享来实现的。线程在其分配的时间范围内在 CPU 内核上执行其代码。它可以被操作系统抢占。它也可能将控制权交给操作系统。
另一方面,协同程序将控制权交给线程内的另一个协同程序,而不是操作系统。因此,一个线程中的所有协程仍然利用该线程的时间框架,而不会将 CPU 内核让给操作系统管理的其他线程。
因此,您可以认为协程由用户而不是操作系统(或准并行)来实现分时。协程在分配给运行这些协程的线程的同一核心上运行。
协程是否实现了并行性?如果它是受 CPU 限制的代码,则不会。就像分时共享一样,它让您感觉它们是并行运行的,但它们的执行是交错的,而不是重叠的。如果它是 IO 绑定的,是的,它通过硬件(IO 设备)而不是您的代码实现并行。
湾。与函数调用的区别?
如图所示,它不需要调用return
来切换控制。它可以在没有return
. 协程在当前函数帧(堆栈)上保存和共享状态。因此,它比函数轻量级得多,因为您不必将寄存器和局部变量保存到堆栈并在call ret
.
协程作为并发的实现和多线程的替代方案。
协程是实现并发的单线程解决方案。
A-Start ------------------------------------------ A-End
| B-Start -----------------------------------------|--- B-End
| | C-Start ------------------- C-End | |
| | | | | |
V V V V V V
1 thread->|<-A-|<--B---|<-C-|-A-|-C-|--A--|-B-|--C-->|---A---->|--B-->|
与多线程解决方案相比:
thread A->|<--A| |--A-->|
thread B------>|<--B| |--B-->|
thread C ---------->|<---C| |C--->|
- 协程是异步编程的一种实现,异步编程是用来实现并发的。
- 许多语言使用协程实现异步编程。其他答案表明 Python、Kotlin、Lua、C++ 已经这样做了。
- 最有用/通常用于涉及 I/O 绑定问题的场景,例如在获取数据时呈现 UI,或从多个数据源下载。
另一方面,pythongevent
库是一个coroutine
基于网络库的网络库,它为您提供异步网络请求等类似线程的功能,而无需创建和销毁线程的开销。使用的coroutine
库是greenlet
.
来自Python 协程:
Python 协程的执行可以在很多时候暂停和恢复(参见协程)。在协程函数体内,await 和 async 标识符成为保留关键字;await 表达式、async for 和 async with 只能在协程函数体中使用。
协程是一个可以暂停执行以稍后恢复的 函数。协程是无堆栈的:它们通过返回调用者来暂停执行。这允许异步执行的顺序代码(例如,在没有显式回调的情况下处理非阻塞 I/O),并且还支持惰性计算无限序列和其他用途的算法。
与其他人的答案比较:
在我看来,后面恢复的部分是核心区别,就像@Twinkle 的一样。
尽管文档的许多领域仍在进行中,但是,这部分与大多数答案相似,除了@Nan Xiao 的
另一方面,协程是协作的:在任何给定时间,具有协程的程序只运行它的一个协程,并且这个正在运行的协程只有在它明确请求暂停时才会暂停其执行。
由于引用自Program in Lua,可能与语言有关(目前对Lua不熟悉),并非所有文档都提到了唯一的一部分。
与并发的关系:协程 (C++20)
中有一个“执行”部分。此处引用太长。
除了细节之外,还有几个状态。
When a coroutine begins execution
When a coroutine reaches a suspension point
When a coroutine reaches the co_return statement
If the coroutine ends with an uncaught exception
When the coroutine state is destroyed either because it terminated via co_return or uncaught exception, or because it was destroyed via its handle
作为@Adam Arold 在@user217714 的回答下的评论。是并发。
但它与多线程不同。
来自 std::thread
线程允许多个函数同时执行。线程在构造相关线程对象后立即开始执行(等待任何 OS 调度延迟),从作为构造函数参数提供的顶级函数开始。顶级函数的返回值被忽略,如果它通过抛出异常终止,则调用 std::terminate。顶级函数可以通过 std::promise 或通过修改共享变量(可能需要同步,参见 std::mutex 和 std::atomic)将其返回值或异常传达给调用者
由于它是并发的,它的工作方式就像多线程,特别是在等待是不可避免的时候(从操作系统的角度来看),这也是它令人困惑的原因。
如果您仍然感到困惑,这里有一个非常简单的理解 a 的方法co-routine
。首先,什么是a routine
?用外行人的话说,例行公事是我们一次又一次地做的事情(例如,你早上的例行公事)。相似地。在编程语言中,aroutine
是一段我们反复使用的代码,例如a function
. 现在,如果您查看 a 的一般特征function or routine
(注意:我谨慎地交替使用这两个术语),只要函数需要输出结果,它就会占用一些输入并占用 CPU 线程。意思是,functions or routines
在你的代码中阻塞调用。然而,一个co-routine
是一种特殊的例程,可以与其他例程同时共存(co-routine 一词的“co”部分来自于此),我们可以在异步编程的帮助下在编程语言中实现这一点。在异步编程中,当一个协程在等待某件事发生时(例如,磁盘 io),另一个协程将开始工作,当这个协程处于等待状态时,另一个协程最终将处于活动状态减少我们代码的等待时间。
如果你理解了上面的内容,让我们看看如何在 Python 中创建协程函数。您可以定义一个协程函数如下 -
async def my_coroutine_function():
return 123
await
您可以通过在协程前面添加来调用上述协程-
my_result = await my_coroutine_function()
总而言之,
当你在看电视节目时,一旦广告出现,你就拿起手机给朋友发短信——你刚刚做的就是异步编程。当您的电视节目(协同程序)处于等待状态时,您继续并让您的其他协同程序(给您的朋友发短信)处于活动状态。
我将扩展 @user21714 的答案。协程是不能同时运行的独立执行路径。它们依赖于控制器(例如python
控制器库)来处理这些路径之间的切换。但是为了使这个工作协程本身需要调用yield
或类似的结构,允许它们的执行被暂停。
相反,线程在独立的计算资源上运行并且彼此并行。由于它们位于不同的资源上,因此无需调用yield来允许其他执行路径继续进行。
您可以通过启动一个多线程程序(例如一个jvm
应用程序)来看到这种效果,其中所有八个超线程内核都被利用:您可能会在或core i7
中看到 797% 的利用率。相反,当运行一个典型的程序时——即使是一个带有或的程序——利用率将达到 100%。即一台机器超线程。Activity Monitor
Top
python
coroutines
python threading
通常我们的想法是——协程是轻量级线程,它们允许我们以同步的方式编写异步、非阻塞代码
至于 Kotlin 协程:
协程是一个合成的糖/附加层,它允许你以非阻塞方式运行一个大任务并且没有回调。协程由一些类( Job
, Dispatcher
, Scope
, Builder
) 和body
让我们回顾一些例子
suspend fun downloadFile(): File {
//logic
}
suspend fun saveFile(file: File) {
//logic
}
GlobalScope.launch {
val downloadResult = downloadFile() //suspend function
show(downloadResult) //UI
saveFile(downloadResult) //suspend function
}
它创建具有功能Continuation
的类state machine
invokeSuspend()
class Continuation {
File file;
void invokeSuspend(Object result) {
switch (label) {
case 0: {
label = 1;
downloadFile(this); //suspend function
return;
}
case 1: {
file = (File) result;
show(file); //UI
saveFile(file, this); //suspend function
return;
}
}
}
}
暂停
- 只是使用的标记
Continuation
- 将延续传递给函数 - 划分状态机,这意味着它可以暂停机器
- 应该在里面使用回调
Continuation.resume() -> Continuation.invokeSuspend()
coroutine
行为完全取决于库实现的要点