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


当前回答

If you are still confused, here is a very simple way of understanding a co-routine. First off, what is a routine? In a lay man's term, a routine is something that we do again and again (for example, your morning routine). Similarly. in programming languages, a routine is a piece of code that we use again and again, e.g., a function. Now, if you look at the general characteristic of a function or routine (note: I am cautiously using these two terms interchangeably), it takes some inputs and hogs the CPU threads for as long as the function needs to output the result. Meaning, functions or routines are blocking calls in you code. However, a co-routine is a special kind of routine that can co-exist (the "co" part of the word co-routine comes from this) with other routines at the same time and we can make this happen in programming languages with the help of asynchronous programming. In Asynchronous programming, when one co-routine is waiting for something to happen (e.g., disk io), the other co-routine will start working and when this co-routine is in a waiting state the other co-routine will be active ultimately reducing the waiting time of our code.

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

async def my_coroutine_function():
    return 123

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

my_result = await my_coroutine_function()

最后,

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

其他回答

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

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

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

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

If you are still confused, here is a very simple way of understanding a co-routine. First off, what is a routine? In a lay man's term, a routine is something that we do again and again (for example, your morning routine). Similarly. in programming languages, a routine is a piece of code that we use again and again, e.g., a function. Now, if you look at the general characteristic of a function or routine (note: I am cautiously using these two terms interchangeably), it takes some inputs and hogs the CPU threads for as long as the function needs to output the result. Meaning, functions or routines are blocking calls in you code. However, a co-routine is a special kind of routine that can co-exist (the "co" part of the word co-routine comes from this) with other routines at the same time and we can make this happen in programming languages with the help of asynchronous programming. In Asynchronous programming, when one co-routine is waiting for something to happen (e.g., disk io), the other co-routine will start working and when this co-routine is in a waiting state the other co-routine will be active ultimately reducing the waiting time of our code.

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

async def my_coroutine_function():
    return 123

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

my_result = await my_coroutine_function()

最后,

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

Kotlin 协程

[同步与异步]

[并发vs并行]

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

至于Kotlin协程:

协程是一个合成糖/附加层,它允许你以非阻塞的方式运行任务,没有回调。协程由以下组件组成:

延续 作业-管理程序和保存它的状态 CoroutineScope(包含作业和Xontext)——处理作业组(层次结构)并管理它们(启动和取消) Dispatcher -用于处理线程池- Main, IO, Default… 上下文-像Map这样的数据结构,以保存一些日期,如Job, Dispatcher和自定义数据

让我们回顾一些例子

class MyClass {
    val network = Network()
    val fileSystem = FileSystem()
    
    suspend fun downloadFile(): File {
        //suspendCoroutine is key point
        return suspendCoroutine { continuation -> 
            network.download(callback: Network.Callback {
                override fun onSuccess(file: File) {
                    continuation.resume(file)
                }
            })
        }
    }

    suspend fun saveFile(file: File) {
        //suspendCoroutine is key point
        return suspendCoroutine { continuation -> 
            fileSystem.save(callback: FileSystem.Callback {
                override fun onSuccess() {
                    continuation.resume()
                }
            })
        }
    }

    GlobalScope.launch {
        val downloadResult = downloadFile() //1. suspend function
        show(downloadResult)                //2. UI 
        saveFile(downloadResult)            //3. suspend function
    }

延续

它创建了Continuation类,它是一个状态机,内部有invokeSuspend()函数。在任何挂起函数的末尾调用invokeSuspend()(像回调一样)

class Continuation {
    int label;

    //block of local variabels
    File file;
 
    void invokeSuspend(Object result) {
        switch (label) {
            case 0: {
                label = 1;
                downloadFile(this);    //1. suspend function
                return;
            }
            case 1: {
                file = (File) result;  //work with result
                show(file);            //2. UI
                saveFile(file, this);  //3.suspend function
                return;
            }
        }
    }
}

class MyClass {
    fun downloadFile(continuation: Continuation): File {
        //logic
        continuation.invokeSuspend(file)
    }

    fun saveFile(file: File, continuation: Continuation) {
        //logic
        continuation.invokeSuspend()
    }
}

暂停

suspended只是一个函数的标记,它将被添加新的延续参数(continue: continue) 分割状态机,这意味着它可以暂停机器 暂停函数应该在Continuation.resume() -> Continuation. invokesuspend()中调用Continuation.resume() 挂起的函数只能从couroutine调用

协程的行为完全依赖于库的实现

来自Python Coroutine:

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

From Coroutines (c++ 20)

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

与他人的答案比较:

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

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

因为引用自Program in Lua,可能是语言相关(目前不熟悉Lua),不是所有文档都只提到一个部分。

与concurrent的关系: 协程(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::线程

Threads allow multiple functions to execute concurrently. Threads begin execution immediately upon construction of the associated thread object (pending any OS scheduling delays), starting at the top-level function provided as a constructor argument. The return value of the top-level function is ignored and if it terminates by throwing an exception, std::terminate is called. The top-level function may communicate its return value or an exception to the caller via std::promise or by modifying shared variables (which may require synchronization, see std::mutex and std::atomic)

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