什么是协程?它们与并发有什么关系?
协程和并发在很大程度上是正交的。协程是一种通用的控制结构,其中流控制在两个不同的例程之间协同传递而不返回。
Python 中的“yield”语句就是一个很好的例子。它创建了一个协程。当遇到“yield”时,保存函数的当前状态并将控制权返回给调用函数。然后调用函数可以将执行转移回屈服函数,其状态将恢复到遇到“屈服”的点并且继续执行。
从 Programming in Lua,“Coroutines
”部分:
协程类似于线程(在多线程的意义上):它是一行执行,有自己的堆栈、自己的局部变量和自己的指令指针;但它与其他协程共享全局变量和大部分其他内容。线程和协程之间的主要区别在于,在概念上(或字面意思,在多处理器机器中),具有线程的程序并行运行多个线程。另一方面,协程是协作的:在任何给定时间,具有协程的程序只运行它的一个协程,并且这个正在运行的协程只有在它明确请求暂停时才会暂停其执行。
所以重点是:协程是“协作的”。即使在多核系统中,任何时候也只有一个协程在运行(但多个线程可以并行运行)。协程之间是非抢占式的,运行中的协程必须显式放弃执行。
对于“concurrency
”,您可以参考 Rob Pike 的 slide:
并发是独立执行计算的组合。
所以在协程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
我发现这个 link 的解释非常直截了当。除了 this answer 中的最后一个要点外,这些答案都没有试图解释并发性与并行性。
什么是并发(程序)?
引自传奇人物乔·阿姆斯特朗的“编程 Erlang”:
并发程序可以在并行计算机上运行得更快。
并发程序是用并发编程语言编写的程序。我们出于性能、可伸缩性或容错性的原因编写并发程序。
并发编程语言是一种具有用于编写并发程序的显式语言结构的语言。这些结构是编程语言的一个组成部分,并且在所有操作系统上的行为方式都相同。
并行计算机是具有多个可以同时运行的处理单元(CPU 或内核)的计算机。
所以并发与并行是不一样的。您仍然可以在单核计算机上编写并发程序。分时调度器会让您感觉您的程序正在同时运行。
并发程序有可能在并行计算机中并行运行,但不能保证。操作系统可能只给你一个内核来运行你的程序。
因此,并发是来自并发程序的软件模型,并不意味着您的程序可以在物理上并行运行。
协程和并发
“协程”一词由两个词组成:“co”(合作)和“routines”(函数)。
一个。它实现并发还是并行?
为简单起见,让我们在单核计算机上讨论它。
并发是通过操作系统的时间共享来实现的。线程在其分配的时间范围内在 CPU 内核上执行其代码。它可以被操作系统抢占。它也可能将控制权交给操作系统。
另一方面,协同程序将控制权交给线程内的另一个协同程序,而不是操作系统。因此,一个线程中的所有协程仍然利用该线程的时间框架,而不会将 CPU 内核让给操作系统管理的其他线程。
因此,您可以认为协程由用户而不是操作系统(或准并行)来实现分时。协程在分配给运行这些协程的线程的同一核心上运行。
协程是否实现了并行性?如果它是受 CPU 限制的代码,则不会。就像分时共享一样,它让您感觉它们是并行运行的,但它们的执行是交错的,而不是重叠的。如果它是 IO 绑定的,是的,它通过硬件(IO 设备)而不是您的代码实现并行。
湾。与函数调用的区别?
https://i.stack.imgur.com/NMETU.png
如图所示,它不需要调用 return
来切换控制。它可以在没有 return
的情况下产生。协程在当前函数帧(堆栈)上保存和共享状态。因此它比函数轻量级得多,因为您不必在 call ret
时将寄存器和局部变量保存到堆栈和回退调用堆栈。
协程是一种特殊的子程序。与传统子程序中存在的调用者和被调用子程序之间的主从关系不同,调用者和被调用协程更加公平。
协程是具有多个条目并自行控制它们的子程序——在 Lua 中直接支持
也称为对称控制:调用者和被调用的协程在更平等的基础上
协程调用被命名为简历
协程的第一次恢复是到它的开头,但随后的调用在协程中最后执行的语句之后的点处进入
协程重复地相互恢复,可能永远
协程提供程序单元(协程)的准并发执行;它们的执行是交错的,但不重叠
https://i.stack.imgur.com/udtJn.png
协程作为并发的实现和多线程的替代方案。
协程是实现并发的单线程解决方案。
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,或从多个数据源下载。
另一方面,在 python 中,gevent
库是一个基于 coroutine
的网络库,它为您提供异步网络请求等类似线程的功能,而无需创建和销毁线程的开销。使用的 coroutine
库是 greenlet
。
如果您仍然感到困惑,这里有一个理解 co-routine
的非常简单的方法。首先,什么是routine
?用外行人的话说,例行公事是我们一次又一次地做的事情(例如,你早上的例行公事)。相似地。在编程语言中,routine
是一段我们反复使用的代码,例如 a function
。现在,如果您查看 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()
总而言之,
当你在看电视节目时,一旦广告出现,你就拿起手机给朋友发短信——你刚刚做的就是异步编程。当您的电视节目(协同程序)处于等待状态时,您继续并让您的其他协同程序(给您的朋友发短信)处于活动状态。
Python 协程的执行可以在很多时候暂停和恢复(参见协程)。在协程函数体内,await 和 async 标识符成为保留关键字; await 表达式、async for 和 async with 只能在协程函数体中使用。
协程是一个可以暂停执行以便稍后恢复的函数。协程是无堆栈的:它们通过返回调用者来暂停执行。这允许异步执行的顺序代码(例如,在没有显式回调的情况下处理非阻塞 I/O),并且还支持惰性计算无限序列和其他用途的算法。
与其他人的答案比较:
在我看来,后面恢复的部分是核心区别,就像@Twinkle 的一样。尽管文档的许多领域仍在进行中,但是,这部分与大多数答案相似,除了@Nan Xiao 的
另一方面,协程是协作的:在任何给定时间,具有协程的程序只运行它的一个协程,并且这个正在运行的协程只有在它明确请求暂停时才会暂停其执行。
由于引用自Program in Lua,可能与语言有关(目前对Lua不熟悉),并非所有文档都提到了唯一的一部分。
与并发的关系:
Coroutines (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 的回答下的评论。它是并发。
但它与多线程不同。 from std::thread
线程允许多个函数同时执行。线程在构造相关线程对象后立即开始执行(等待任何 OS 调度延迟),从作为构造函数参数提供的顶级函数开始。顶级函数的返回值被忽略,如果它通过抛出异常终止,则调用 std::terminate。顶级函数可以通过 std::promise 或通过修改共享变量(可能需要同步,参见 std::mutex 和 std::atomic)将其返回值或异常传达给调用者
由于它是并发的,它的工作方式就像多线程,特别是在等待是不可避免的时候(从操作系统的角度来看),这也是它令人困惑的原因。
我将扩展 @user21714 的答案。协程是不能同时运行的独立执行路径。它们依赖于控制器(例如 python
控制器库)来处理这些路径之间的切换。但要使其工作,协程本身需要调用 yield
或允许暂停执行的类似结构。
相反,线程在独立的计算资源上运行并且彼此并行。由于它们位于不同的资源上,因此无需调用 yield 来允许其他执行路径继续进行。
您可以通过启动一个多线程程序(例如 jvm
应用程序)看到此效果,其中所有八个 core i7
超线程内核都被利用:您可能会看到 Activity Monitor
或 Top
的利用率为 797%。相反,在运行典型的 python
程序时——即使是带有 coroutines
或 python threading
的程序——利用率将达到 100%。即一个机器超线程。
通常我们的想法是——协程是轻量级线程,它们允许我们以同步的方式编写异步、非阻塞代码
至于 Kotlin 协程:
协程是一个合成糖/附加层,它允许您以非阻塞方式和无回调运行大型任务。协程由一些类(Job
、Dispatcher
、Scope
、Builder
)和 body
https://i.stack.imgur.com/EZMsx.png
让我们回顾一些例子
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 传递给函数
划分状态机,这意味着它可以暂停机器
应该使用回调里面调用 Continuation.resume() -> Continuation.invokeSuspend()
coroutine
的行为完全取决于库实现的要点
不定期副业成功案例分享
Coroutines are a general control structure whereby flow control is cooperatively passed between two different routines without returning.
<-- 这是并发。您正在寻找的词是并行性。orthogonal = Not similar to each other
?orthogonal
的意思是“彼此独立”。