liveBook · Manning

Cooperative multitasking

As explained in Grokking Concurrency

Cooperative multitasking is a multitasking technique in which running tasks voluntarily yield control so other tasks can execute.

Imagine a busy host taking an order, sending it to the kitchen, then helping another diner while the food cooks. Cooperative multitasking works similarly: a running task pauses voluntarily so another task can make progress. The task must choose when to yield; the cooperative scheduler cannot take control away from it. section 12.0 section 12.3

In preemptive multitasking, the operating system can pause a thread. In a cooperative system, tasks instead hand control to an event loop at chosen points. Coroutines make this practical: each can pause, retain its progress, and resume later, allowing many tasks to share one execution thread. section 12.3 section 12.4

Taking turns in Python

A small Python event loop keeps coroutines in a queue. It resumes the next one, then puts it back in the queue if it has yielded rather than finished. A Fibonacci coroutine prints one value per turn, making each handoff visible. 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 queue, send(None) call, and StopIteration handler show how the loop resumes, reschedules, and retires coroutines. section 12.4

The same pattern can handle a server: its coroutine yields while accepting a connection, and an event loop coordinates the resulting client tasks within one thread. This is especially useful when many tasks spend time waiting for I/O, such as network requests. section 12.9 section 12.4

The trade-off is control over the handoff:

  • Lightweight user-level tasks can cost less to manage and switch than system threads. section 12.5
  • Choosing yield points can keep another task from interrupting a critical section midway through. section 12.5
  • A long-running task that never yields can hold up every other task. Break such work into smaller chunks or return control periodically; concurrency alone does not make that work faster. section 12.5

How Grokking Concurrency covers it

How deep it goes
Works through coroutine scheduling in a Python event loop and applies it to a socket server.
Examples
Demonstrates turn-taking with a Fibonacci coroutine and applies it to a single-threaded socket server.
What it assumes
Introduces user-level threads by contrasting them with operating-system threads; uses Python code for the worked implementations.
What you can do after
Build it from scratch with Python coroutines and an event loop, then apply it to an asynchronous socket server.
How it connects
Connects user-level threads, coroutines, event loops, asynchronous I/O, and preemptive multitasking.

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.