协程

title: 同子例程的比较.
content: 协程可以通过yield(取其“让步”之义而非“出产”)来调用其它协程,接下来的每次协程被调用时,从协程上次yield返回的位置接着执行,通过yield方式转移执行权的协程之间不是调用者与被调用者的关系,而是彼此对称、平等的。由于协程不如子例程那样被普遍所知,下面对它们作简要比较: 子例程可以看作是特定状况的协程,任何子例程都可转写为不调用yield的协程。
title: 示例.
content: 这里是一个简单的例子证明协程的实用性。假设这样一种生产者-消费者的关系,一个协程生产产品并将它们加入队列,另一个协程从队列中取出产品并消费它们。伪码表示如下:  队列用来存放产品的空间有限,同时制约生产者和消费者:为了提高效率,生产者协程要在一次执行中尽量向队列多增加产品,然后再放弃控制使得消费者协程开始运行;同样消费者协程也要在一次执行中尽量从队列多取出产品,从而倒出更多的存放产品空间,然后再放弃控制使得生产者协程开始运行。尽管这个例子常用来介绍多线程,实际上简单明了的使用协程的yield即可实现这种协作关系。
title: 同线程的比较.
content: 协程非常类似于线程。但是协程是协作式多任务的,而线程典型是抢占式多任务的。这意味着协程提供并发性而非并行性。协程超过线程的好处是它们可以用于硬性实时的语境(在协程之间的切换不需要涉及任何系统调用或任何调用),这里不需要用来守卫关键区段的同步性原语(primitive)比如互斥锁、信号量等,并且不需要来自操作系统的支持。有可能以一种对调用代码透明的方式,使用抢占式调度的线程实现协程,但是会失去某些利益(特别是对硬性实时操作的适合性和相对廉价的相互之间切换)。  纤程是协作式多任务的轻量级线程,本质上描述了同协程一样的概念。其区别,如果一定要说有的话,是协程是语言层级的构造,可看作一种形式的控制流,而纤程是系统层级的构造,可看作恰巧没有并行运行的线程。这两个概念谁有优先权是争议性的:纤程可看作为协程的一种实现,也可看作实现协程的基底。
title: 生成器.
content: 生成器,也叫作“半协程”,是协程的子集。尽管二者都可以yield多次,挂起(suspend)自身的执行,并允许在多个入口点重新进入,但它们特别差异在于,协程有能力控制在它让位之后哪个协程立即接续它来执行,而生成器不能,它只能把控制权转交给调用生成器的调用者。在生成器中的codice_1语句不指定要跳转到的协程,而是向父例程传递返回值。尽管如此,仍可以在生成器设施之上实现协程,这需要通过顶层的派遣器(dispatcher)例程(实质上是)的援助,它显式的把控制权传递给由生成器传回的令牌(token)所标识出的子生成器。  在不同作者和语言之间,术语“生成器”和“迭代器”的用法有着微妙的差异。有人说所有生成器都是迭代器,生成器看起来像函数而表现得像迭代器。在Python中,生成器是迭代器构造子:它是返回迭代器的函数。
title: 同尾调用互递归的比较.
content: 使用协程用于状态机或并发运行类似于使用经由尾调用的互递归,在二者情况下控制权都变更给一组例程中的另一个不同例程。但是,协程更灵活并且一般而言更有效率。因为协程是yield而非return返回,接着恢复执行而非在起点重新开始,它们有能力保持状态,包括变量(同于闭包)和执行点二者,并且yield不限于位于尾部位置;互递归子例程必须要么使用共享变量,要么把状态作为参数传递。进一步的说,每一次子例程的互递归调用都需要一个新的栈帧(除非实现了尾调用消去),而在协程之间传递控制权使用现存上下文并可简单地通过跳转来实现。
title: 协程之常见用例.
content: 协程有助于实现:
title: 支持协程的编程语言.
content: 协程起源于一种汇编语言方法,但有一些高级编程语言支持它。早期的例子包括Simula、Smalltalk和Modula-2。更新近的例子是Ruby、Lua、Julia和Go。  由于续体可被用来实现协程,支持续体的编程语言也非常容易就支持协程。
title: 实现.
content: 到2003年,很多最流行的编程语言,包括C语言和它的后继者,都未在语言内或其标准库中直接支持协程。(这在很大程度上是受基于堆栈的子例程实现的限制)。C++的Boost.Context库是个例外,它是Boost C++ Libraries的一部分,它在POSIX、Mac OS X和Windows上支持ARM、MIPS、PowerPC、SPAR和x86的上下文切换。可以在Boost.Context之上建造协程。  在协程是某种机制的最自然的实现方式,却不能获得可用协程的情况下,典型的解决方法是使用闭包,它是用状态变量(静态变量常为布尔标志值)来在调用之间维持内部状态,并转移控制权至正确地点的子例程。基于这些状态变量的值,在代码中的条件语句导致在后续调用时有着不同代码路径的执行。另一种典型的解决方法实现一个显式状态机,采用某种形式的大量而复杂的或goto语句特别是“计算goto”。这种实现被认为难于理解和维护,更是想要有协程支持的动机。  在当今的主流编程环境里,协程的合适的替代者是线程和适用范围较小的纤程。线程提供了用来管理“同时”执行的代码段实时协作交互的功能,在支持C语言的环境中,线程是广泛有效的,POSIX.1c(IEEE Std 1003.1c-1995)规定了被称为pthreads的一个标准线程API,它在类Unix系统中被普遍实现。线程被很好地实现、文档化和支持,很多程序员对其也比较熟悉。但是,线程包括了许多强大和复杂的功能用以解决大量困难的问题,这导致了困难的学习曲线,当任务仅需要协程就可完成时,使用线程似乎就是用力过猛了。GNU Pth可被视为类Unix系统上纤程的代表,有人尝试过用Windows的纤程机制实现协程。
title: C语言实现.
content: C标准库里有“非局部跳转”函数setjmp和longjmp,它们分别保存和恢复:栈指针、程序计数器、被调用者保存的寄存器和ABI要求的任何其他内部状态。在C99标准中,跳转到已经用codice_2或codice_3终止的函数是未定义的<ref name="ISO/IEC 9899:1999">ISO/IEC 9899:1999, 2005, 7.13.2.1:2 and footnote 211</ref>,但是大多数codice_3实现在跳转时不专门销毁调用栈中的局部变量,在被后续的函数调用等覆写之前跳转回来恢复时仍是原样,这允许在实现协程时谨慎的用到它们。  POSIX.1-2001/SUSv3和此前的SUSv2进一步提供了操纵的强力设施:,可方便地用来实现协程,但是由于codice_5的参数定义不符合C99标准要求,这些函数在POSIX.1-2004中被废弃,并在POSIX.1-2008中被删除。POSIX.1-2001/SUSv3和此前的SUSv2定义了codice_6,可用来在不能获得codice_5的情况下稍微迂回的实现协程。极简实现不采用有关的标准API函数进行上下文交换,而是写一小块内联汇编只对换栈指针和程序计数器故而速度明显的要更快。  由于缺乏直接的语言支持,很多作者写了自己的含藏上述技术细节的协程库,以Russ Cox的libtask协程库为代表,它自称能够“写事件驱动程序而没有麻烦的事件”,并可用在FreeBSD、Linux、Mac OS X和SunOS之上。知名的实现还有:libpcl、lthread、libCoroutine、libconcurrency、libcoro、ribs2、libdill、libaco、libco等等。  此外人们做了用C语言的子例程和宏实现协程的大量尝试,并取得了不同程度的成功。Simon Tatham作出的贡献是这一方法的很好示例,它受到了达夫设备利用swtich语句“掉落”特性的启发,并且是Protothreads和类似实现的基础。这种方法的确可以提高代码段的可写性、可读性,但可维护性是存在争议的。这种不为每个协程维护独立的栈帧的实现方式主要缺点是,局部变量在经过从函数yield之后是不保存的,控制权只能从顶层例程yield。
title: Python实现.
content: Python 2.5基于扩展的生成器实现对类似协程功能的更好支持。Python 3.3通过支持委托给子生成器增进了这个能力。Python 3.4介入了综合性的异步I/O框架准化,包括了利用子生成器委托的协程。Python 3.5通过async/await语法介入了对协程的显式支持。从Python 3.7开始async/await成为保留关键字。
title: Tcl实现.
content: 从 Tcl 8.6 开始,Tcl 核心内置协程支持,成为了继事件循环、线程后的另一种内置的强大功能。

Summary: 协程()是计算机程序的一类组件,推广了协作式多任务的子程序,允许执行被挂起与被恢复。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。协程更适合于用来实现彼此熟悉的程序组件,如协作式多任务、异常处理、事件循环、迭代器、无限列表和管道。

Coroutine: Dans un programme, une coroutine est une unité de traitement qui s'apparente à une routine, à ceci près que, alors que la sortie d'une routine met fin à la routine, la sortie de la coroutine peut être le résultat d'une suspension de son traitement jusqu'à ce qu'il lui soit signalé de reprendre son cours. La suspension de la coroutine et la reprise de son cours peuvent s'accompagner d'une transmission de données.