Chapter Eleven · Concurrency

Concurrency

Several flows of control sharing one machine, and what that sharing costs. Six topics separate concurrency from parallelism, take the lost update apart, price the lock that prevents it and the deadlocks it invites, go down to the atomic instructions and memory ordering underneath, and end on the alternatives to locking, from event loops to message passing.

6 topics

Chapter 10 gave each program a private machine. This chapter lets several flows of control share one, and the sharing is where the trouble starts. Two threads adding one to the same counter can produce one increment instead of two, with no error, no crash and no sign in the logs, and the loss grows with traffic. Every bug in this chapter is a version of that one.

The chapter first separates concurrency, many tasks in progress, from parallelism, many tasks running at the same instant, and states the law that caps what more cores can buy. It then shows the lost update with Lantern's "searches today" counter, runs it on Python 3.15, and prices the three kinds of fix. A lock turns the gap into a queue and invites deadlock; one layer down, atomic instructions and the memory model explain what a lock is made of and why the same code can fail on one processor and pass on another.

The last topic covers the alternatives to locking: event loops and async and await, which still share everything but switch only at an await; channels and actors, which avoid shared memory altogether; and Python's global interpreter lock, which is now optional. Choosing a server's concurrency model in practice stays with Backend Deep Dive. This chapter is the mechanism underneath that choice.

From one shared counter to the three ways out
Many tasksone state
→
The gapread, then write
→
Lock or atomicqueue or no gap
→
Stop sharingoften cheapest

Topics in This Chapter