什么是协程?

什么是协程?它们与并发性有什么关系?

91002 次浏览

协程和并发在很大程度上是正交的。协程是一种通用的控制结构,流控制在两个不同的例程之间协作传递而不返回。

Python中的'yield'语句就是一个很好的例子。它创建了一个协程。当遇到yield时,将保存函数的当前状态,并将控制返回给调用函数。然后,调用函数可以将执行转移回屈服函数,它的状态将恢复到遇到“屈服”的位置,并继续执行。

用Lua编程, "Coroutines"部分:

协程类似于线程(在多线程的意义上):它是一个执行行,有自己的堆栈,自己的局部变量和自己的指令指针;但是它与其他协程共享全局变量和其他大部分内容。线程和协程之间的主要区别在于,在概念上(或者字面上,在多处理器机器中),具有线程的程序并行运行几个线程。另一方面,协程是协作的:在任何给定的时间,具有协程的程序只运行其中一个协程,并且只有当它显式地请求挂起时,这个正在运行的协程才挂起它的执行。

所以重点是:协程是“协作的”。即使在多核系统中,也只有一个协程在任何给定时间运行(但多个线程可以并行运行)。协程之间存在不可抢占性,运行中的协程必须显式放弃执行。

对于"concurrency",你可以参考Rob Pike的幻灯片:

并发是独立执行计算的组合。

因此在协程A的执行过程中,它将控制权传递给协程B。然后过了一段时间,协程B将控制权传递回协程A。由于协程之间存在依赖,并且它们必须串联运行,所以这两个协程为没有并发

协程类似于子例程/线程。 区别在于,一旦调用方调用了子例程/线程,它将永远不会返回到调用方函数。 但是协程可以在执行几段代码后返回到调用方,允许调用方执行一些自己的代码,并返回到它停止执行的协程点,并从那里继续。 ie。协程有多个入口点和出口点

另一方面, gevent library是一个基于coroutine的网络库,它为您提供线程类功能,如异步网络请求,而无需创建和销毁线程的开销。使用的coroutine库是greenlet.

  • 协程是Kotlin语言中可用的很棒的特性
  • 协程是一种新的异步、非阻塞写入方式 Code(以及更多) . txt 协程是轻量级线程。一根轻的线就意味着它 不映射到本机线程,因此不需要上下文切换 在处理器上,所以它们更快。
  • 它不映射到本机线程上
  • 协程和线程都是多任务处理。但是区别在于 线程由操作系统管理,协程由用户管理

基本上,有两种类型的协程:

  1. Stackless
  2. Stackful
Kotlin实现了无堆栈协程-这意味着 协程没有自己的堆栈,所以它们不会映射到本机线程上

这些是启动协程的函数:

launch{}


async{}

你可以在这里了解更多:

https://www.kotlindevelopment.com/deep-dive-coroutines/

https://blog.mindorks.com/what-are-coroutines-in-kotlin-bf4fecd476e9

我发现大多数答案都太专业了,尽管这是一个技术问题。我很难理解协同程序的过程。我有点明白,但我不能同时明白。

我发现这个答案非常有用:

< a href = " https://dev。/ thibmaek / explain-coroutines-like-im-five-2d9 noreferrer“rel = > https://dev.to/thibmaek/explain-coroutines-like-im-five-2d9 < / >

引用伊丹·阿耶的话:

以你的故事为基础,我想这样说:

你开始看卡通片,但它是介绍。而不是 看介绍你切换到游戏和进入在线大厅- 但它需要3个玩家,只有你和你的妹妹在里面。而不是 等待另一个玩家加入你开始做作业,然后 回答第一个问题。第二个问题有YouTube的链接 你要看的视频。你打开它,它开始载入。而不是 等待它加载,你切换回卡通。的介绍 结束了,所以你可以看着。现在有广告,但同时 有第三个玩家加入了,所以你切换到游戏中,以此类推。

这个想法是,你不只是快速切换任务 看起来你在同时做所有的事情。你利用了时间 你在等待事情发生(IO)去做其他会做的事情

一定要检查链接,还有更多我不能引用的东西。

Python协同程序:

Python协程的执行可以挂起和恢复 点(见协程)。在协程函数体内,等待 异步标识符成为保留关键字;等待表达式, Async for和Async with只能在协程函数中使用 身体。< / p >

协同程序(C + + 20)

协程是一个函数,它可以挂起执行,使恢复 晚些时候。协程是无堆栈的:它们通过返回来暂停执行 给打电话的人。这允许执行顺序代码 异步(例如,不显式地处理非阻塞I/O 回调),并且还支持惰性计算无限上的算法

与他人的答案比较:

在我看来,恢复后部分是一个核心的区别,就像@Twinkle的 虽然文档的很多字段还在完善中,但是这一部分和大部分的答案是相似的,除了@南晓的

另一方面,协程是协作的:在任何给定的时间,a 带有协程的程序只运行其中一个协程,并且 这个正在运行的协程仅在显式执行时才会暂停执行

.请求被暂停

由于它引用自Lua中的Program,可能与语言相关(目前不熟悉Lua),并非所有文档都提到了只有一个部分。

< p > 与concurrent的关系: < br > 协同程序(C + + 20)中有一个“Execution”部分。太长了,这里不能引用
除了细节,还有几个状态
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的回答下的评论。这是并发。< br > 但它与多线程不同。 从std::线程 < / p >

线程允许多个函数并发执行。 类的构造之后,线程立即开始执行 关联线程对象(挂起任何操作系统调度延迟),开始 在作为构造函数参数提供的顶级函数中。的 顶层函数的返回值将被忽略,如果它终止 通过抛出异常,std::terminate被调用。顶级 函数可以将其返回值或异常传递给 调用者通过std::promise或修改共享变量(这可能 需要同步,参见std::mutex和std::atomic)

因为它是并发的,它就像多线程一样工作,特别是当等待是不可避免的(从操作系统的角度来看),这也是为什么它令人困惑的原因。

我将详述@user21714的答案。协程是独立的执行路径,不能同时运行。它们依赖于一个控制器——例如python控制器库——来处理这些路径之间的切换。但是为了实现这一点,协程本身需要调用yield或类似的结构,以允许它们的执行被暂停。

相反,线程运行在独立的计算资源上,并且彼此并行。因为它们在不同的资源上,所以不需要调用收益率来允许其他执行路径继续进行。

你可以通过启动一个多线程程序——例如,一个jvm应用程序——在这个程序中,你的所有八个core i7超线程核心都被利用了:你可能会看到Activity MonitorTop的利用率为797%。相反,当运行典型的python程序时——即使是带有coroutinespython threading的程序——利用率最高将达到100%。即一个机器超线程。

我发现这个链接的解释非常直接。这些答案都没有试图解释并发和并行,除了这个答案中的最后一个要点。

  1. 什么是并发(程序)?

引用选自《programming Erlang"》,作者Joe Armstrong,传奇人物:

并发程序在并行计算机上可能运行得更快。

  • 并发程序是用并发编程语言编写的程序。我们编写并发程序是出于性能、可伸缩性或容错的考虑。

  • 并行编程语言是一种具有用于编写并发程序的显式语言构造的语言。这些构造是编程语言不可分割的一部分,在所有操作系统上都以相同的方式运行。

  • 并行计算机是指具有多个可以同时运行的处理单元(cpu或核心)的计算机。

所以并发性和并行性是不同的。您仍然可以在单核计算机上编写并发程序。分时调度器会让你感觉你的程序正在并发运行。

并发程序具有在并行计算机中并行运行的潜力,但是没有保证的。操作系统可能只给你一个内核来运行程序。

因此,并发是一种软件模型来自一个并发程序,并不意味着你的程序可以在物理上并行运行。

  1. 协程和并发

“协程”一词由两个词组成:“co”(合作)和“routine”(函数)。

A.它实现了并发还是并行?

为了简单起见,让我们在单芯计算机上讨论它。

并发性是通过来自操作系统的分时实现的。线程在CPU内核上按照指定的时间框架执行代码。它可以被OS抢占。它也可以将控制权交给操作系统。

另一方面,协程将控制权交给线程内的另一个协程,而不是交给OS。因此,线程内的所有协程仍然利用该线程的时间框架,而不会将CPU核心交给由操作系统管理的其他线程。

因此,你可以考虑协程通过用户而不是操作系统实现分时(或准并行)。协程运行在分配给运行这些协程的线程的同一个核心上。

协程能实现并行吗?如果它是cpu绑定的代码,则不会。就像分时系统一样,它让你感觉它们是平行运行的,但它们的执行是交错的,而不是重叠的。如果它是IO绑定的,是的,它通过硬件(IO设备)实现并行,而不是通过您的代码。

B.与函数调用的区别?

enter image description here

如图所示,它不需要调用return来切换控制。它可以在没有return的情况下屈服。协程保存并共享当前函数帧(堆栈)上的状态。因此,它比函数要轻量级得多,因为你不必保存寄存器和局部变量来堆栈,并在call ret时倒带调用堆栈。

[同步vs异步]

[并发vs并行]

通常我们认为协程是轻量级线程,它们允许我们以同步的方式编写异步的、非阻塞的代码

至于Kotlin协程:

协程是一个合成糖/附加层,它允许你在非阻塞的方式没有回调中运行一个大任务。协程由一些类(JobDispatcherScopeBuilder)和body组成

enter image description here

让我们回顾一些例子

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类,该类是带有invokeSuspend()函数的state machine

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的行为完全依赖于库实现

协程作为并发性的实现和多线程的替代方案。

协程是实现并发的单线程解决方案。

         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,或从多个数据源下载。

如果你仍然困惑,这里有一个非常简单的方法来理解co-routine。首先,什么是routine?用外行的话来说,例行公事就是我们一遍又一遍地做的事情(例如,你早上的例行公事)。类似的。在编程语言中,routine是我们反复使用的一段代码,例如a function。现在,如果你看一下function or routine的一般特征(注意:我谨慎地交替使用这两个术语),它需要一些输入,只要函数需要输出结果,它就会占用CPU线程。意思是,functions or routines阻塞了你代码中的调用。然而,co-routine是一种特殊的例程,可以共存("co"“协同例程”这个词的一部分来自于这个),同时与其他例程一起使用,我们可以在编程语言中借助异步编程实现这一点。在异步编程中,当一个协例程正在等待某些事情发生时(例如磁盘io),另一个协例程将开始工作,当这个协例程处于等待状态时,另一个协例程将处于活动状态,最终减少代码的等待时间。

如果你理解了上面的内容,让我们看看如何在Python中创建协程函数。可以像下面那样定义协程函数-

async def my_coroutine_function():
return 123


你可以通过在协程-前面添加await来调用上面的协程

my_result = await my_coroutine_function()

最后,

当你正在看电视节目,广告一出现,你就拿起手机给朋友发短信——你刚刚做的就是异步编程。当你的电视节目(一个合作程序)处于等待状态时,你继续前进,让你的另一个合作程序(给你的朋友发短信)处于激活状态。