【问题标题】:What is a coroutine?什么是协程?
【发布时间】:2011-01-27 00:57:29
【问题描述】:

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

【问题讨论】:

  • 并发代码不一定非要“并行”运行(暂且不引入新名词)。
  • 我用标准 C 编写了一个协程库,支持 Linux、BSD 和 Windows 的 select/poll/eplll/kqueue/iocp/Win GUI 消息。这是github.com/acl-dev/libfiber 中的一个开源项目。欢迎提出建议。
  • 更多有趣的信息在这里:stackoverflow.com/q/16951904/14357
  • 我可以想象,如果在当前时代提出这个问题,它会被否决。不知道为什么社区的看法与以前相比有如此巨大的差异?
  • 一个协程是一个可以在返回之前暂停执行的函数,它可以在一段时间内间接将控制权交给另一个协程。

标签: concurrency language-agnostic terminology coroutine


【解决方案1】:

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

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

【讨论】:

  • 直接调用一个函数和从一个协程中产生并把这个函数包装到这个协程中有什么区别?
  • 在这种情况下,最好解释一下这两个概念并不是真正的“正交”。你绝对可以画出这两个概念是如何相似的。在两个或多个事物之间传递控制的想法非常相似。
  • Coroutines are a general control structure whereby flow control is cooperatively passed between two different routines without returning. 是并发。您正在寻找的词是并行性。
  • @steviejay orthogonal = Not similar to each other?
  • @tonix 有人告诉我orthogonal 的意思是“相互独立”。
【解决方案2】:

来自Programming in Lua,“Coroutines”部分:

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

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

对于“concurrency”,可以参考 Rob Pike 的slide

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

所以在协程A的执行过程中,它把控制权交给了协程B。一段时间后,协程B又把控制权交给了协程A。由于协程之间存在依赖,它们必须运行在串联,所以这两个协程不并发

【讨论】:

  • 协程不独立执行。他们轮流进行,每个人都在等待对方完成部分工作。他们积极地相互协调。这与 Rob Pikes 对并发的定义相反。
  • @ErickG.Hagstrom:虽然它们不独立执行,但每个协程的逻辑都可以是独立的,对吧?如果是对的,它就像一个运行在单核 CPU 上的非抢占式操作系统,一个进程必须让出 CPU 让其他任务运行。
  • 放弃 CPU 让 其他任务 运行和告诉其他特定进程它是时候执行是有区别的。协程做后者。这在任何意义上都不是独立的。
  • @ChrisClark 我同意你的看法。协程是并发的。以下是来自维基百科的引用:协程与线程非常相似。然而,协程是协同多任务的,而线程通常是抢先式多任务的。这意味着它们提供并发但不提供并行
  • 并且:协作多任务,也称为非抢占式多任务,是一种计算机多任务处理方式,其中操作系统从不启动从正在运行的进程到另一个进程的上下文切换。相反,进程会定期或在空闲或逻辑阻塞时自愿让出控制权,以使多个应用程序能够同时运行。
【解决方案3】:

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

【讨论】:

  • 它与线程不太相似——线程独立且同时运行(并行的独立内核)。此外,子程序比较失败,因为有多个独立的执行路径,并且它们没有相互返回结果。
【解决方案4】:

换一种说法, 在 python 中,gevent 库是一个基于coroutine 的网络库,它为您提供异步网络请求等线程特性,而无需创建和销毁线程的开销。使用的coroutine 库是greenlet

【讨论】:

    【解决方案5】:
    • 协程是 Kotlin 语言中可用的强大功能
    • 协程是一种编写异步、非阻塞的新方法 代码(以及更多)
    • 协程是轻量级线程。轻量级线程意味着它 不在本机线程上映射,因此不需要上下文切换 在处理器上,因此它们更快。
    • 它没有映射到本机线程
    • 协程和线程都是多任务处理。但是区别 是线程由操作系统管理,协程由用户管理。

    Coroutines 基本上有两种类型:

    1. 无堆栈
    2. 堆叠式

    Kotlin 实现了无堆栈协程 —— 这意味着 协程没有自己的堆栈,因此它们不会映射到本机线程。

    这些是启动协程的函数:

    launch{}
    
    async{}
    

    您可以从这里了解更多信息:

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

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

    【讨论】:

    • 好答案!对 Kotlin 和 Android 开发人员很有用。
    【解决方案6】:

    我发现大多数答案都过于技术化,尽管这是一个技术问题。我很难理解协程过程。我有点明白,但我没有同时明白。

    我发现这里的答案很有帮助:

    https://dev.to/thibmaek/explain-coroutines-like-im-five-2d9

    引用 Idan Arye 的话:

    为了建立你的故事,我会这样说:

    你开始看动画片,但它是介绍。代替 看介绍你切换到游戏进入在线大厅—— 但它需要 3 名玩家,而且只有你和你的妹妹在里面。反而 等待其他玩家加入你切换到你的作业,和 回答第一个问题。第二个问题有一个 YouTube 的链接 您需要观看的视频。你打开它 - 它开始加载。反而 等待它加载,你切换回卡通。介绍 完结了,大家可以看看。现在有广告 - 但同时 第三个玩家已经加入,所以你切换到游戏等等......

    我们的想法是,您不仅要快速切换任务 看起来你正在同时做所有事情。你利用时间 你正在等待某件事发生(IO)来做其他事情 需要您的直接关注。

    一定要检查链接,还有很多我不能引用所有内容。

    【讨论】:

    • 非常简单直接的插图。为此 +1。
    • 很棒的插图。我建立了一个类似的故事——排队等候领取包裹。但就今天而言,你的情况要现实得多,当有door2door 送货时,谁在排队?大声笑
    • 这个解释真棒。从报价本身来看,非常清楚。
    • 这使得这里的所有其他解释更有意义。代码是一组 CPU 指令。协程允许指令在等待 CPU 外的任务完成时继续执行
    • 我发现我无法理解这里的这句话,但理解“技术”描述。我认为这个例子只是让人们觉得他们理解它而不是真正理解它。
    【解决方案7】:

    来自Python Coroutine

    Python 协程的执行可以在很多情况下暂停和恢复 点(参见协程)。在协程函数体内,等待 并且异步标识符成为保留关键字;等待表达式, async for 和 async with 只能在协程函数中使用 身体。

    来自Coroutines (C++20)

    协程是一个可以暂停执行以恢复的函数 稍后。协程是无堆栈的:它们通过返回来暂停执行 给来电者。这允许执行的顺序代码 异步(例如,在没有显式的情况下处理非阻塞 I/O 回调),还支持惰性计算无限算法 序列和其他用途。

    与其他人的答案比较:

    在我看来,稍后恢复部分是核心区别,就像@Twinkle's。
    尽管文档的许多字段仍在进行中,但是,这部分与大多数答案相似,除了@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)

    因为它是并发的,所以它就像多线程一样工作,尤其是在等待是不可避免的时候(从操作系统的角度来看),这也是它令人困惑的原因。

    【讨论】:

      【解决方案8】:

      我将扩展 @user21714 的答案。协程是不能同时运行的独立执行路径。它们依赖于控制器——例如python 控制器库——来处理这些路径之间的切换。但要使其正常工作,协程本身需要调用 yield 或允许暂停执行的类似结构。

      线程反而在独立的计算资源上运行并且彼此并行。由于它们位于不同的资源上,因此无需调用 yield 来允许其他执行路径继续进行。

      您可以通过启动多线程程序来看到这种效果 - 例如jvm 应用程序 - 其中所有八个 core i7 超线程内核都已使用:您可能会在 Activity MonitorTop 中看到 797% 的利用率。相反,当运行典型的python 程序时——即使是带有coroutinespython threading 的程序——利用率将达到100%。 IE。 一个机器超线程。

      【讨论】:

        【解决方案9】:

        协程是一种特殊的子程序。而不是主从 调用者和被调用子程序之间的关系与常规子程序一样,调用者和被调用协程更加公平。

        • 协程是具有多个 条目并自行控制它们 - 支持 直接在 Lua 中

        • 也称为对称控制:调用者和被调用者 协程更加平等

        • 协程调用被命名为简历

        • 协程的第一次恢复是开始, 但随后的呼叫在紧随其后的点进入 协程中最后执行的语句

        • 协程重复地相互恢复,可能 永远

        • 协程提供准并发执行 程序单元(协程);他们的执行是 交错,但不重叠

        【讨论】:

          【解决方案10】:

          我发现这个link 的解释非常简单。除了this answer 中的最后一个要点之外,这些答案都没有试图解释并发与并行。

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

          引用来自传奇人物 Joe Armstrong 的“programming Erlang”:

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

          • 并发程序是用并发编程语言编写的程序。我们出于性能、可扩展性或容错性的原因编写并发程序。

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

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

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

          并发程序有可能在并行计算机上并行运行,但不保证。操作系统可能只给你一个内核来运行你的程序。

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

          1. 协程和并发

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

          一个。是实现并发还是并行?

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

          并发是通过操作系统的分时实现的。线程在其分配的时间范围内在 CPU 内核上执行其代码。它可以被操作系统抢占。它还可以将控制权交给操作系统。

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

          因此,您可以认为协程由用户而不是操作系统(或准并行)来实现分时。协程在分配给运行这些协程的线程的同一内核上运行。

          协程是否实现了并行性?如果它是受 CPU 限制的代码,则不会。就像分时共享一样,它让您感觉它们是并行运行的,但它们的执行是交错的,而不是重叠的。如果它是 IO 绑定的,是的,它通过硬件(IO 设备)而不是您的代码实现并行。

          b.与函数调用的区别?

          如图所示,切换控制不需要调用return。它可以在没有return 的情况下产生。协程在当前函数帧(堆栈)上保存和共享状态。所以它比函数更轻量级,因为您不必在call ret 时将寄存器和局部变量保存到堆栈和回退调用堆栈。

          【讨论】:

            【解决方案11】:

            [Synchronous vs Asynchronous]

            [Concurrency vs Parallelism]

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

            关于 Kotlin 协程:

            协程是一个合成糖/附加层,它允许您以非阻塞方式无回调运行大型任务。协程由一些类组成(JobDispatcherScopeBuilder)和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 machineinvokeSuspend() 函数

            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 的行为完全取决于库实现的要点

            【讨论】:

              【解决方案12】:

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

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

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

              【讨论】:

                【解决方案13】:

                如果您仍然感到困惑,这里有一个非常简单的方法来理解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()
                

                总结一下,

                当您正在观看电视节目时,一旦广告出现,您就会拿起手机给朋友发短信 - 您刚刚所做的就是异步编程。当您的电视节目(一个协同程序)处于等待状态时,您继续进行并让您的另一个协同程序(给您的朋友发短信)处于活动状态。

                【讨论】:

                  猜你喜欢
                  • 2020-11-17
                  • 1970-01-01
                  • 2019-07-24
                  • 2017-09-16
                  • 2011-04-28
                  • 2020-02-14
                  • 2017-08-06
                  • 2016-05-30
                  相关资源
                  最近更新 更多