Understanding Coroutines: From Ordinary Functions to User‑Level Threads
This article explains the concept of coroutines, compares them with ordinary functions, shows Python code examples of yielding and resuming execution, illustrates their historical background, and describes how they are implemented using heap‑allocated stack frames to achieve lightweight, user‑level concurrency.
As a programmer you may have heard the term coroutine , especially in high‑performance and high‑concurrency contexts, but you might not know exactly what it means. This article is written to give you a complete understanding of coroutines.
Ordinary function example
A simple function that prints three letters:
def func():
print("a")
print("b")
print("c")When called, the function runs from start to finish and then returns, producing the output:
a
b
cFrom ordinary functions to coroutines
Unlike a normal function, a coroutine can have multiple return (or pause) points. In pseudo‑C syntax the function looks like:
void func() {
print("a");
// pause and return
print("b");
// pause and return
print("c");
}When the first pause is reached, control returns to the caller while the coroutine’s state is saved, allowing it to resume later from that point.
Python coroutine example
Using yield in Python turns the function into a coroutine:
def func():
print("a")
yield
print("b")
yield
print("c")It can be used as follows:
def A():
co = func() # obtain the coroutine object
next(co) # run until first yield (prints "a")
print("in function A")
next(co) # resume, prints "b"The output demonstrates that after the first next call the coroutine pauses, the surrounding function continues, and a subsequent next call resumes execution from the previous pause point.
Graphical explanation
Diagrams compare a normal function call stack with coroutine execution, showing how control returns to the caller after each yield and later resumes from the saved point.
Coroutines are a special case of functions
In essence, a coroutine is a function that can be suspended and later resumed; a regular function is simply a coroutine without any suspension points.
Historical background
The idea of coroutines dates back to 1958, predating threads. Early implementations appeared in Simula 67 and Scheme (1972). Their popularity waned with the advent of OS‑level threads, but they resurfaced in recent years due to the need for massive concurrency in modern services.
How coroutines are implemented
When a coroutine is paused, its execution context (the stack frame) must be saved. This is typically done by allocating the stack frame on the heap instead of the traditional call stack, allowing the runtime to store and restore the context without copying data back and forth.
Illustrations show multiple coroutines sharing a single OS thread, each with its own heap‑allocated stack, enabling thousands of concurrent execution flows with minimal overhead.
Conclusion
Coroutines provide user‑level threading: they allow many lightweight execution flows, preserve their state across pauses, and give programmers explicit control over scheduling, making them ideal for high‑concurrency applications.
High Availability Architecture
Official account for High Availability Architecture.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.