ChatGPT解决这个技术问题 Extra ChatGPT

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

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

u
user21714

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

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 的意思是“彼此独立”。
N
Nan Xiao

Programming in Lua,“Coroutines”部分:

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

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

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

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

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


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

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

我发现这个答案非常有帮助:

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

引用 Idan Arye 的话:

为了建立你的故事,我会这样说:你开始看卡通片,但它是介绍。不用看介绍,而是切换到游戏并进入在线大厅 - 但它需要 3 名玩家,而且只有你和你的妹妹在里面。无需等待其他玩家加入,而是切换到您的作业,并回答第一个问题。第二个问题有一个指向您需要观看的 YouTube 视频的链接。你打开它 - 它开始加载。您无需等待它加载,而是切换回卡通。介绍已经结束,大家可以观看。现在有广告 - 但同时第三位玩家加入了,所以你切换到游戏等等......这个想法是你不只是非常快速地切换任务,让它看起来你在一次做所有事情。您利用等待某事发生的时间(IO)来做其他需要您直接关注的事情。

一定要检查链接,还有更多我无法引用的内容。


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

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


它与线程不太相似——线程独立且同时运行(并行的独立内核)。此外,子例程比较失败,因为存在多个独立的执行路径并且它们没有相互返回结果。
D
Dhaval Jivani

协程是 Kotlin 语言中可用的强大功能

协程是一种编写异步、非阻塞代码(以及更多)的新方法

协程是轻量级线程。轻量级线程意味着它不会映射到本机线程,因此它不需要处理器上的上下文切换,因此它们更快。

它不映射到本机线程

协程和线程都是多任务处理。但不同之处在于线程由操作系统管理,协程由用户管理。

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

无堆叠堆叠

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

这些是启动协程的函数:

launch{}

async{}

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

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

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


好答案!对 Kotlin 和 Android 开发人员很有用。
I
Izana

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

什么是并发(程序)?

引自传奇人物乔·阿姆斯特朗的“编程 Erlang”:

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

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

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

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

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

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

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

协程和并发

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

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

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

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

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

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

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

湾。与函数调用的区别?

https://i.stack.imgur.com/NMETU.png

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


K
Konrad Rudolph

协程是一种特殊的子程序。与传统子程序中存在的调用者和被调用子程序之间的主从关系不同,调用者和被调用协程更加公平。

协程是具有多个条目并自行控制它们的子程序——在 Lua 中直接支持

也称为对称控制:调用者和被调用的协程在更平等的基础上

协程调用被命名为简历

协程的第一次恢复是到它的开头,但随后的调用在协程中最后执行的语句之后的点处进入

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

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

https://i.stack.imgur.com/udtJn.png


u
user1473249581

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

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

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


j
joseph

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


H
Heapify

如果您仍然感到困惑,这里有一个理解 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()

总而言之,

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


S
Shihe Zhang

Python Coroutine

Python 协程的执行可以在很多时候暂停和恢复(参见协程)。在协程函数体内,await 和 async 标识符成为保留关键字; await 表达式、async for 和 async with 只能在协程函数体中使用。

来自Coroutines (C++20)

协程是一个可以暂停执行以便稍后恢复的函数。协程是无堆栈的:它们通过返回调用者来暂停执行。这允许异步执行的顺序代码(例如,在没有显式回调的情况下处理非阻塞 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)将其返回值或异常传达给调用者

由于它是并发的,它的工作方式就像多线程,特别是在等待是不可避免的时候(从操作系统的角度来看),这也是它令人困惑的原因。


W
WestCoastProjects

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

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

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


y
yoAlex5

[Synchronous vs Asynchronous]

[Concurrency vs Parallelism]

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

至于 Kotlin 协程:

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

划分状态机,这意味着它可以暂停机器

应该使用回调里面调用 Continuation.resume() -> Continuation.invokeSuspend()

coroutine的行为完全取决于库实现的要点