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


当前回答

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

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

您可以通过启动一个多线程程序(例如jvm应用程序)来看到这种效果,其中所有八个核心i7超线程核心都被利用了:您可能会在Activity Monitor或Top中看到797%的利用率。相反,当运行一个典型的python程序(即使是带有协程或python线程的程序)时,利用率最高将达到100%。例如,一台机器超线程。

其他回答

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

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

在Lua编程中,“协程”部分:

A coroutine is similar to a thread (in the sense of multithreading): it is a line of execution, with its own stack, its own local variables, and its own instruction pointer; but it shares global variables and mostly anything else with other coroutines. The main difference between threads and coroutines is that, conceptually (or literally, in a multiprocessor machine), a program with threads runs several threads in parallel. Coroutines, on the other hand, are collaborative: at any given time, a program with coroutines is running only one of its coroutines, and this running coroutine suspends its execution only when it explicitly requests to be suspended.

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

关于“并发性”,你可以参考Rob Pike的幻灯片:

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

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

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调用

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

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

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

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

您可以通过启动一个多线程程序(例如jvm应用程序)来看到这种效果,其中所有八个核心i7超线程核心都被利用了:您可能会在Activity Monitor或Top中看到797%的利用率。相反,当运行一个典型的python程序(即使是带有协程或python线程的程序)时,利用率最高将达到100%。例如,一台机器超线程。