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


当前回答

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

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

其他回答

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()

最后,

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

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

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

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

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

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

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

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

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

什么是并发(程序)?

引用自Joe Armstrong的“编程Erlang”,传奇人物:

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

a concurrent program is a program written in a concurrent programming language. We write concurrent programs for reasons of performance, scalability, or fault tolerance. a concurrent programming language is a language that has explicit language constructs for writing concurrent programs. These constructs are an integral part of programming language and behave the same way on all operating systems. a parallel computer is a computer that has several processing units (CPUs or cores) that can run at the same time.

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

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

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

协程和并发

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

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

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

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

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

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

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

B.与函数调用的区别?

如图所示,它不需要调用return来切换控制。它可以不计回报地付出。协程保存并共享当前函数帧(堆栈)上的状态。因此,它比函数轻量级得多,因为你不需要保存寄存器和局部变量来堆栈,并在调用ret时倒带调用stack。