Coroutine
Coroutines are computer program components that allow execution to be suspended and resumed, generalizing subroutines for cooperative multitasking. Coroutines are well-suited for implementing familiar program components such as cooperative tasks, exceptions, event loops, iterators, infinite lists and pipes.
They have been described as "functions whose execution you can pause".
Melvin Conway coined the term coroutine in 1958 when he applied it to the construction of an assembly program. The first published explanation of the coroutine appeared later, in 1963.
Definition and types
There is no single precise definition of coroutine. In 1980 Christopher D. Marlin summarized two widely-acknowledged fundamental characteristics of a coroutine:- the values of data local to a coroutine persist between successive calls;
- the execution of a coroutine is suspended as control leaves it, only to carry on where it left off when control re-enters the coroutine at some later stage.
- the control-transfer mechanism. Asymmetric coroutines usually provide keywords like
yieldandresume. Programmers cannot freely choose which frame to yield to. The runtime only yields to the nearest caller of the current coroutine. On the other hand, in symmetric coroutines, programmers must specify a yield destination. - whether coroutines are provided in the language as first-class objects, which can be freely manipulated by the programmer, or as constrained constructs;
- whether a coroutine is able to suspend its execution from within nested function calls. Such a coroutine is a stackful coroutine. One to the contrary is called stackless coroutines, where unless marked as coroutine, a regular function can't use the keyword
yield.
Comparisons
Subroutines
Subroutines are special cases of coroutines. When subroutines are invoked, execution begins at the start, and once a subroutine exits, it is finished; an instance of a subroutine only returns once, and does not hold state between invocations. By contrast, coroutines can exit by calling other coroutines, which may later return to the point where they were invoked in the original coroutine; from the coroutine's point of view, it is not exiting but calling another coroutine. Thus, a coroutine instance holds state, and varies between invocations; there can be multiple instances of a given coroutine at once. The difference between calling another coroutine by means of "yielding" to it and simply calling another routine, is that the relationship between two coroutines which yield to each other is not that of caller-callee, but instead symmetric.Any subroutine can be translated to a coroutine which does not call yield.
Here is a simple example of how coroutines can be useful. Suppose you have a consumer-producer relationship where one routine creates items and adds them to a queue and another removes items from the queue and uses them. For reasons of efficiency, you want to add and remove several items at once. The code might look like this:
var q := new queue
coroutine produce
loop
while q is not full
create some new items
add the items to q
yield to consume
coroutine consume
loop
while q is not empty
remove some items from q
use the items
yield to produce
call produce
The queue is then completely filled or emptied before yielding control to the other coroutine using the yield command. The further coroutines calls are starting right after the yield, in the outer coroutine loop.
Although this example is often used as an introduction to multithreading, two threads are not needed for this: the yield statement can be implemented by a jump directly from one routine into the other.
Threads
Coroutines are very similar to threads. However, coroutines are cooperatively multitasked, whereas threads are typically preemptively multitasked. Coroutines provide concurrency, because they allow tasks to be performed out of order or in a changeable order, without changing the overall outcome, but they do not provide parallelism, because they do not execute multiple tasks simultaneously. The advantages of coroutines over threads are that they may be used in a hard real-time context, there is no need for synchronization primitives such as mutexes, semaphores, etc. in order to guard critical sections, and there is no need for support from the operating system.It is possible to implement coroutines using preemptively-scheduled threads, in a way that will be transparent to the calling code, but some of the advantages will be lost.
Generators
Generators, also known as semicoroutines, are a subset of coroutines. Specifically, while both can yield multiple times, suspending their execution and allowing re-entry at multiple entry points, they differ in coroutines' ability to control where execution continues immediately after they yield, while generators cannot, instead transferring control back to the generator's caller. That is, since generators are primarily used to simplify the writing of iterators, theyield statement in a generator does not specify a coroutine to jump to, but rather passes a value back to a parent routine.However, it is still possible to implement coroutines on top of a generator facility, with the aid of a top-level dispatcher routine that passes control explicitly to child generators identified by tokens passed back from the generators:
var q := new queue
generator produce
loop
while q is not full
create some new items
add the items to q
yield
generator consume
loop
while q is not empty
remove some items from q
use the items
yield
subroutine dispatcher
var d := new dictionary
d := start consume
d := start produce
var current := produce
loop
call current
current := next d
call dispatcher
A number of implementations of coroutines for languages with generator support but no native coroutines use this or a similar model.
Mutual recursion
Using coroutines for state machines or concurrency is similar to using mutual recursion with tail calls, as in both cases the control changes to a different one of a set of routines. However, coroutines are more flexible and generally more efficient. Since coroutines yield rather than return, and then resume execution rather than restarting from the beginning, they are able to hold state, both variables and execution point, and yields are not limited to being in tail position; mutually recursive subroutines must either use shared variables or pass state as parameters. Further, each mutually recursive call of a subroutine requires a new stack frame, while passing control between coroutines uses the existing contexts and can be implemented simply by a jump.Common uses
Coroutines are useful to implement the following:- State machines within a single subroutine, where the state is determined by the current entry/exit point of the procedure; this can result in more readable code compared to use of goto, and may also be implemented via mutual recursion with tail calls.
- Actor model of concurrency, for instance in video games. Each actor has its own procedures, but they voluntarily give up control to central scheduler, which executes them sequentially.
- Generators, and these are useful for streamsparticularly input/outputand for generic traversal of data structures.
- Communicating sequential processes where each sub-process is a coroutine. Channel inputs/outputs and blocking operations yield coroutines and a scheduler unblocks them on completion events. Alternatively, each sub-process may be the parent of the one following it in the data pipeline.
- Reverse communication, commonly used in mathematical software, wherein a procedure such as a solver, integral evaluator,... needs the using process to make a computation, such as evaluating an equation or integrand.
Native support
- Aikido
- AngelScript
- Ballerina
- BCPL
- Pascal
- BETA
- BLISS
- C++
- C#
- Chapel
- ChucK
- CLU
- D
- Dynamic C
- Erlang
- F#
- Factor
- GameMonkey Script
- GDScript
- Haskell
- High Level Assembly
- Icon
- Io
- JavaScript ECMAScript 2017 also includes await support.
- Julia
- Kotlin
- Limbo
- Lua
- Lucid
- μC++
- Modula-2
- Nemerle
- Perl 5
- PHP
- Picolisp
- Prolog
- Python
- Racket
- Raku
- Ruby
- Sather
- Scheme
- Self
- Simula 67
- Smalltalk
- Squirrel
- Stackless Python
- SuperCollider
- Tcl
- urbiscript
kotlinx.coroutines.Since continuations can be used to implement coroutines, programming languages that support them can also quite easily support coroutines.