Lecture
Coroutine (from English coroutine) — a program module organized in a special way to provide interaction with other modules on the principle of cooperative multitasking: the module suspends at a certain point, saving its complete state (including the call stack and instruction pointer), and passes control to another one, which, in turn, performs its task and passes control back, saving its own stack and instruction pointer. Along with fibers (English fiber), coroutines are a means of providing “lightweight” program multithreading in the sense that they can be implemented without using operating-system context-switching mechanisms.
Coroutines are more flexible and generalized than subroutines: compared to a subroutine, which always has a single entry point, a coroutine has a starting entry point, after which a sequence of returns is placed inside it; each return is also endowed with its own entry point. A subroutine can return only once, a coroutine can return control several times. The runtime of a subroutine is determined by the LIFO principle (the last subroutine called finishes first), whereas the runtime of a coroutine is determined by the need for it to run.
The emergence of the concept of the coroutine is attributed to a construction used by Melvin Conway in 1958 in the practice of assembly-language programming ; in the 1960s — 1970s coroutines were used in some high-level languages (CLU, Simula, Modula-2), but gained noticeable spread only in the 2000s, when numerous coroutine-support libraries appeared in popular programming languages. In some newer languages (such as Lua, Ruby, Go, Julia) coroutine-support libraries are built in from the start. Coroutines are used to implement many similar program components, such as generators and iterators, infinite lists using lazy evaluation, channels, finite state machines inside a single subroutine (where the state is determined by the current entry and exit point), implementations of exception handling, and the actor model.
Coroutines are useful for implementing the following:
A significant portion of popular programming languages, including C and its derivatives (C++ prior to C++20), do not have direct support for coroutines in the language or the standard library (this is due, for the most part, to the requirements of the stack-based implementation of subroutines).
In a situation where coroutines, as a natural way of implementing components, are unavailable, a typical solution is to create coroutines using a set of boolean flags and other state variables to maintain external state between calls. Conditions inside the code lead to the execution of different instruction sequences on successive calls according to the values of the state variables. Another typical solution is to implement a finite state machine manually using a large switch statement. Such implementations are difficult to maintain and support.
Threads are a suitable alternative to coroutines in most modern development. Threads provide capabilities for managing the interaction of pieces of code executing “simultaneously”. Therefore this is a solution to large and complex problems, it includes powerful, comprehensive capabilities and has an accompanying learning complexity. However, despite other alternatives, threads are widely available in the C environment, are familiar to most programmers, and are usually implemented, documented and maintained.
Some attempts to implement coroutines in C:
One of the approaches used to implement coroutines in languages without built-in support for them — stackless protothreads, which provide a blocking context at the cost of a few bytes of memory per thread.
Functional programming languages often implement coroutines, for example, Scheme, Lisp, Haskell. In a number of languages, built-in support for coroutines was added in later releases, such as Python (starting with 2.5, and with explicit syntactic support starting with 3.5), PHP (starting with 5.5), Kotlin (from version 1.1), JavaScript (from version 1.7), C# (from version 2.0), Tcl (from version 8.6).
Coroutines
Coroutines are more generalized than subroutines. A distinguishing feature of coroutines is the presence of several entry points, as depicted in figure 5. A subroutine always has a single entry point, a coroutine has a starting entry point and a sequence of returns and the entry points following them placed inside it. A subroutine can return only once, a coroutine can return control several times. After a subroutine is called again, execution starts from the starting entry point, whereas in a coroutine it starts from the place where control was passed.

Figure 5 - Distinguishing features of a coroutine compared to a subroutine.
Thanks to coroutines, the code looks as though it runs in a single thread of execution. As a result, ease of writing programs, ease of maintenance and bug catching are increased. It is worth noting that processor time is spent on creating and managing coroutines, so performance is 10%-15% lower than with a purely asynchronous approach .
There are two different implementations of coroutines:
StackFull is a more performant implementation of coroutines, but also the most complex. This implementation exists only in the Boost.Coroutine, Boost.Context libraries of the C++ language. The other programming languages use the StackLess implementation.
Effective use of asynchronous operations
Asynchronous operations are the most effective method of increasing the performance of a dispatch center, which spends most of its time on I/O operations. This method was tested by me on a dispatch-center project for a high-precision object-positioning system. The number of clients whose requests are handled by the server increased by 40%. This is a very significant performance gain with minimal labor costs. The correct choice of programming language contributed to this. In this case it is C#, which in its fifth version gained support for asynchronous operations by means of coroutines.
Comments