Jump to content

User:A876/Coroutine

From Wikipedia, the free encyclopedia

These are additional pieces originally intended for the article Coroutine. There is much that needs to be said and clarified in that article. I tried, but i did not get it right. See Talk:Coroutine#called vs. peer coroutines

[intro...]

Two distinct kinds of functionality are implied by the term "coroutine". It is not always clear which functionality is being discussed or utilized. What they have in common is, the coroutine preserves its internal state of execution, not just its static variables. A coroutine resembles a spearate executing program.

Called coroutine:

A coroutine is called and it returns just like a subroutine, except that it preserves its internal execution state between calls. A subroutine has one entry point and can use any number of exit points (return statements). When a subroutine is called again, it begins execution at the top. A coroutine has one "initial" entry point and can use any number of return statements. When a coroutine is called "for the first time", it begins execution at the top. When a coroutine is called again, it resumes execution at the point immediately after the return statement that concluded the last call. The coroutine preserves its internal execution state between calls. That lets it do some interesting things. For example, code like "return 5; return 7; return 9;" actually means something, and it is much simpler code than a state-machine subroutine that could do the same thing. (Since there is a "first time" provision, there could be a special action to re-initialize the coroutine. In object-oriented scenarios, there would be ways to discard an instance of a coroutine and create a new instance.) (Multiple explicit entry points could allow the calling statement could specify where the coroutine should begin or resume executing. But multiple entry can also apply to subroutines, and is a different topic.) (Arguments passed to a called coroutine could update the formal parameters every time the coroutine is called.) A called coroutine can return a sequence of values by returning a different value each time it is called. C# includes a new keyword, "yield return" that can be used within a declared IEnumerator that allows it to continue executing the next time it is called. [1]

Peer coroutines:

A coroutine can yield control to a specified peer coroutine. When a coroutine receives control "for the first time", it begins execution at the top. When a coroutine receives control again, it resumes execution at the point immediately after the Yield statement that released control the last time. Each peer coroutine preserves its internal execution state while it is inactive.

Puzzles arise when trying to combine a called coroutine with peer coroutines. If the called coroutine yields to a peer coroutine, the peer coroutine could return a value. On the next call, where will execution continue? This can be resolved by grouping the peer coroutines as a set, or by not allowing returns from the peer coroutine, only the one that was called.

Comparison of coroutines and subroutines

[edit]

The main program can call a subroutine or it can call into a set of peer coroutines. (Coroutines are thought of as sets because each peer coroutine names one or more of its peers.)

When calling a subroutine, control passes to the top of the subroutine. At the end of every subroutine (and optionally in the middle as well) is a return command, which returns control to the caller.

When calling into a a set of coroutines, control passes to the top of the first coroutine or the specified coroutine. Somewhere within at least one of the coroutines is a return command, which returns control to the caller. Every coroutine must end with either a yield command or a return command.

The yield command is used only between coroutines. It resembles both the call command and the return command, but it is different from both.

The executing coroutine can, at any time, explicitly yield control to any other coroutine. When yielding to another coroutine, control passes to the other coroutine wherever it left off. (If the other coroutine has not executed previously, then it begins execution from the top.) Control might return to the coroutine that yielded, but it might not. If control ever does return, it is not definite from which other coroutine control will be returned. (Assuming there are more than two coroutines in the set.)

The yield command resembles the call command because the coroutine executing it gives up control and might get it back. The yield command resembles the return command, because the coroutine that is yielded to merely resumes executing. Yield is like a return command that does not use the stack, but instead resumes executing the specified named procedure. (Any number of coroutines might be suspended.)

Each coroutine can be re-entered any number times. Each yield is not tracked at all, as there is no reverse of yield. The overhead for coroutines is minimal. There is no context switching at all. Only the addition of an IP (Instruction Pointer) for each coroutine is required to track the re-entry point. The Yield command simply saves the IP of the next instruction in the current coroutine and then jumps to the IP stored for the target coroutine. Also, the entry point for the set of coroutines must reset all of the IPs before execution can begin.

... The real power of coroutines becomes evident in more complicated examples. If the first coroutine has TWO yield commands, then when control is yielded back to it, it has to continue from the right place. In that case, the functioning of the yield command can no longer be reduced to a simple jump command.

Detailed comparison

[edit]

Since coroutines can have more points of entry than subroutines, it is possible to implement any subroutine as a lone coroutine. "Subroutines are special cases of ... coroutines." —Donald Knuth

Each time a subroutine is called (invoked), execution starts at the beginning of the invoked subroutine. The first time a coroutine is invoked, execution starts at the beginning of the coroutine. However, each time a coroutine is resumed (by use of the yield command), execution resumes following the place where the coroutine last (yielded from).

A set of coroutines can accept arguments and return values, just as a subroutine.

Coroutines do not pass arguments to each other. The yield command does not have arguments because there is no place in the resumed code to accept them. Still, a coroutine can be useful to generate a series of numbers

Since a subroutine returns only once, returning multiple values requires returning a collection of values. (Some languages, like Forth and Perl allow convenient returning of collections, while other languages like C only permit a single return value, which then needs to be a reference to a collection of values.) In contrast, since coroutines can yield multiple times, passing multiple values merely requires returning additional values upon subsequent calls to the coroutine. (This is a trivial use of coroutines, which could be raplced by inline code.) Routines in which subsequent calls yield additional results are often known as generators.

Subroutines only require a single stack that can be preallocated at the beginning of program execution. In contrast, coroutines, able to call on other coroutines as peers, are best implemented using continuations. Continuations may require allocation of additional stacks and therefore are more commonly implemented in garbage-collected high-level languages. Coroutine creation can be done cheaply by preallocating stacks or caching previously allocated stacks.


Threads can explicitly release the processor by "sleeping". Threads can reactivate sleeping threads by waking them up. The wake-up is not immediate, it depends on the scheduler. Coroutines can hand off control instantly.