63

The concept of a coroutine sounds very interesting, but I don't know, if it makes sense in a real productive environment? What are use cases for coroutines, where the coroutine implementation is more elegant, simpler or more efficient than other methods?

Rob Bednark
  • 25,981
  • 23
  • 80
  • 125
Mnementh
  • 50,487
  • 48
  • 148
  • 202

9 Answers9

58

One use case is a web server that has multiple simultaneous connections, with a requirement to schedule reading and writing in parallel with all of them.

This can be implemented using coroutines. Each connection is a coroutine that reads/writes some amount of data, then yields control to the scheduler. The scheduler passes to the next coroutine (which does the same thing), cycling through all the connections.

Rob Bednark
  • 25,981
  • 23
  • 80
  • 125
Ali Afshar
  • 40,967
  • 12
  • 95
  • 109
  • 7
    I don't know why this languished without a +1 for so long. A coroutine-driven web server, presuming the routines were designed correctly for piecemeal computation, would kick ass on a heavyweight threaded web server when it comes to throughput and would be a whole lot easier to understand than a state machine-managed one. – JUST MY correct OPINION May 17 '10 at 02:21
  • 1
    considering the OS can tell you what connections need your attention, this seems an inefficient approach. – xaxxon May 28 '17 at 04:47
  • @AliAfshar one of the best use of coroutines! – anekix Mar 28 '18 at 06:48
38

Use case: coroutines are often used in game programming to time-slice computations.

To maintain a consistent frame rate in a game, e.g., 60 fps, you have about 16.6ms to execute code in each frame. That includes physics simulation, input processing, drawing/painting.

Lets say your method is executed in every frame. If your method takes a long time and ends up spanning multiple frames, you are going to stagger the rest of the computation in the game loop which results in the user seeing "jank" (a sudden drop in frame rate).

Coroutines make it possible to time slice the computation so that it runs a little bit in each frame.

For that to happen, coroutines allow the method to "yield" the computation back to the "caller" (in this case the game loop) so that the next time the method is called it resumes from where it left off.

Rob Bednark
  • 25,981
  • 23
  • 80
  • 125
numan salati
  • 19,394
  • 9
  • 63
  • 66
29

Unix pipes are a use case:

grep TODO *.c | wc -l

The pipeline above is a coroutine. The grep command generates a sequence of lines and writes them to a buffer. The wc command reads these lines from the buffer. If the buffer fills up, then grep "blocks" until the buffer empties. If the buffer is empty, then wc waits for more input in the buffer.

Coroutines are more often used in more constrained patterns, like the Python generators mentioned, or as pipelines.

For more details and examples, read the Wikipedia articles, particularly coroutines and iterators.

Rob Bednark
  • 25,981
  • 23
  • 80
  • 125
Charlie Martin
  • 110,348
  • 25
  • 193
  • 263
20

True coroutines require language support. They need to be implemented by the compiler and supported by the underlying framework.

One language-supported implementation of coroutines is the C# 2.0 yield return keyword, which allows you to write a method that returns multiple values for looping.

The yield return does have limitations, however. The implementation uses a helper class to capture state, and it only supports the specific case of a coroutine as a generator (iterator).

In a more general case, an advantage of coroutines is that they make certain state-based computations easier to express and easier to understand. For example, implementing a state machine as a set of coroutines can be more elegant than other implementations. But doing this requires language support that doesn't yet exist in C# or Java.

Rob Bednark
  • 25,981
  • 23
  • 80
  • 125
Bevan
  • 43,618
  • 10
  • 81
  • 133
  • Is it? In C#, yield return is used in an iterator method to define an iterator block. It allows the method to return a sequence of values. Isn't this different from a coroutine which can be suspended and resumed? – gingerbreadboy Jul 18 '23 at 18:16
16

Coroutines are useful to implement producer/consumer patterns.

For example, Python introduced coroutines in a language feature called generators, which was intended to simplify the implementation of iterators.

They can also be useful to implement cooperative multitasking, where each task is a coroutine that yields to a scheduler/reactor.

ddaa
  • 52,890
  • 7
  • 50
  • 59
  • I can't comment on Python's generators, but I've used a generator construct before, and I found the concept nifty with great toy problems, but very hard to use in actual coding. – Paul Nathan Nov 19 '08 at 23:42
  • 2
    Generators are very handy and widely used in today's Python. They can produce much simpler, more readable code than the equivalent written with an object, putting state information in members. But they are not full co-routines and have their limitations in comparison. – bobince Nov 20 '08 at 00:24
13

Coroutines can be useful when a system performs two or more tasks that would be most naturally described as a series of long-running steps that involve a lot of waiting.

For example, consider a device which has an LCD-and-keypad user interface and a modem, and it needs to use the modem to periodically call and report its status independent of what the user at the keypad is doing. The nicest way to write the user interface may be to use functions like "input_numeric_value(&CONV_SPEED_FORMAT, &conveyor_speed);" which will return when a user has entered a value, and the nicest way to handle the communication may be use functions like "wait_for_carrier();" which will return when the unit has either connected or determined it's not going to.

Without coroutines, either the UI subsystem or the modem subsystem would have to be implemented using a state machine. Using coroutines makes it possible for both subsystems to be written in the most natural style. Note that it's important that neither subsystem ever goes very long without putting things into a "consistent" state and calling yield(), nor calls yield() without putting things into a "consistent" state first, but it's usually not hard to meet those constraints.

Note that while one could use full-blown multitasking, that requires widespread use of locks or other mutual-exclusion constructs almost anyplace that shared state is altered. Since the coroutine switcher won't ever switch things except at yield() calls, either routine can freely alter shared state so long as it ensures that everything in in order before the next yield, and is prepared for the other routine to alter state "during" the yield().

Rob Bednark
  • 25,981
  • 23
  • 80
  • 125
supercat
  • 77,689
  • 9
  • 166
  • 211
  • You are not the only one to mention state machines. Why exactly are coroutines replacements for state machines? ELI5 – Iizuki Nov 03 '21 at 14:59
  • 1
    @Iizuki: One can implement state machines within coroutines, and many systems have enough simple state machines that it would be silly to replace all of them with coroutines. The big advantage of using coroutines is that code using them can be written in a much more normal style. For example, if one one has a "putchar" function that sends a byte out a serial port if the hardware is ready, or else simply does a task spin, one could use something like `printf("The position is (%d, %d)", x, y);` and not have it block other tasks from executing. Using a state machine, one would have to... – supercat Nov 03 '21 at 15:25
  • 1
    ...either have a buffer that could accommodate the entire message, or else have a small buffer to handle each decimal output and have the main state machine format either x or y into that buffer at appropriate times. Using coroutines, the extra buffer would only need to be allocated between the time the code starts formatting a number and when it's done, while using state machines it would probably need to be allocated statically. – supercat Nov 03 '21 at 15:33
7

As a producer/consumer example, a batch reporting program be implemented with coroutines.

The key hint for that example is having nontrivial work to consume input data (e.g. parsing data or accumulating charges and payments on an account), and nontrivial work to produce the output. When you have these characteristics, then:

  • It is easy to organize/understand the input-side code if you can write units of work at various places.
  • It is likewise easy to organize/understand the output-side code if it can read the next unit of work in a nested control structure.

then coroutines and queues are both nice techniques to have at your disposal.

Rob Bednark
  • 25,981
  • 23
  • 80
  • 125
joel.neely
  • 30,725
  • 9
  • 56
  • 64
1

One use case: In-Memory DataBase Systems.

Coroutines can be used for reducing CPU stalls due to cache misses caused by lookups in such database systems, like hash probes or B+-tree traversals.

Database systems use many pointer-based data structures, including hash tables and B+-trees, which require extensive "pointer-chasing" during a hash probe or a B+-tree traversal. When a batch of operations arrive at the same time, Coroutines can easily interleave the instruction flow between operations, thereby reducing CPU stalls caused by cache misses, taking advantage of the parallelism of memory access.

cypredar
  • 11
  • 2
1

A quick look at the answers above gives me the impression that they focus on execution speed and that one of the other interesting features of true coroutines is being ignored: that they can communicate values. For sure in low-level code, and in Python.

One use case would be to pit two game-playing programs against each other -- each would submit its next move to the other. The advantage is that the code reads more naturally: each coroutine regards the other as a function -- send a move and receive and answer. The same sort of thing arises in simulations, and probably other problems.

In contrast, the examples above send data in only one direction (generators) if they send any data at all.

4dummies
  • 759
  • 2
  • 9
  • 22