liveBook · Manning

Coroutine

As explained in Grokking Concurrency

A coroutine is a computation or function whose execution can be paused and later resumed, enabling cooperative asynchronous control flow.

Imagine ordering a burger, then doing something else while it cooks. A coroutine works much like that: it is a function that can pause partway through, let another task run, and later resume where it stopped. section 12.4 section 12.6

This is cooperative multitasking. An operating system can preempt a thread, but a running coroutine must hand over control itself. Pausing while a network request waits lets another task use the same thread. section 12.4

Taking turns with Fibonacci

A Python Fibonacci generator pauses between values. An event loop resumes it, puts it back in a task queue if unfinished, and eventually observes its completion. The example makes the handoff visible one loop cycle at a time. section 12.4

# Chapter 12/coroutine.py
from collections import deque
import typing as T

Coroutine = T.Generator[None, None, int]

class EventLoop:
    def __init__(self) -> None:
        self.tasks: T.Deque[Coroutine] = deque()              #1

    def add_coroutine(self, task: Coroutine) -> None: 
        self.tasks.append(task)                               #2

    def run_coroutine(self, task: Coroutine) -> None:
        try:
            task.send(None)                                   #3
            self.add_coroutine(task)
        except StopIteration:                                 #4
            print("Task completed")

    def run_forever(self) -> None:                            #5
        while self.tasks:                                     #5
            print("Event loop cycle.")                        #5
            self.run_coroutine(self.tasks.popleft())          #5

def fibonacci(n: int) -> Coroutine:
    a, b = 0, 1
    for i in range(n):
        a, b = b, a + b
        print(f"Fibonacci({i}): {a}")
        yield                                                 #6
    return a                                                  #7

if __name__ == "__main__":
    event_loop = EventLoop()
    event_loop.add_coroutine(fibonacci(5)) 
    event_loop.run_forever()

The small Python event loop shows how a deque holds tasks, send(None) resumes one coroutine, and StopIteration marks completion. section 12.4

For an operation whose result is not ready yet, a coroutine can yield a Future—a placeholder for that result. In a nonblocking socket server, the event loop tracks tasks waiting for I/O and resumes them when the relevant socket event occurs. section 12.6 section 12.8

The trade-off is control:

  • Coroutines can share one thread with less task-management overhead than many system threads, particularly for numerous I/O tasks. section 12.5
  • A long-running task can hold up every other coroutine unless it periodically yields; the event loop cannot interrupt it. section 12.5 section 12.8

How Grokking Concurrency covers it

How deep it goes
Defines pause and resume, then works through generator scheduling and a Future-based socket event loop.
Examples
Uses a Fibonacci generator, a burger order, and an asynchronous pizza server.
What it assumes
Introduces coroutine pause and resume and future results before combining them in a cooperative server.
What you can do after
Build it from scratch with Python generators, futures, and an event loop to coordinate a nonblocking socket server.
How it connects
Connects cooperative multitasking to preemptive multitasking, event loops, futures, and nonblocking sockets.

Also explained in

Compare how 8 books explain coroutine →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Grokking Concurrency. The section links point to the parts of the book it draws on.