Chapter 6 — Concurrency

Topics 33–36 | Pages 289–323


Core Idea

Concurrency = execution that acts as if simultaneous (e.g., via threads, fibers).
Parallelism = execution that is simultaneous (multiple cores/machines).

Modern software must handle concurrency — users, external APIs, and data arrive asynchronously. The challenge: shared mutable state turns simple code into a source of subtle bugs.


Topic 33 — Breaking Temporal Coupling (p292)

Tip 56: Analyze Workflow to Improve Concurrency

Temporal coupling = code that imposes a sequence that the problem doesn’t require. “Method A must always be called before B” when there’s no actual reason for that ordering.

Consequences: slow systems, brittle dependencies, bottlenecks that don’t need to exist.

How to find concurrency opportunities: Draw an activity diagram of your workflow. Identify which steps must happen in sequence vs. which are independent. Often, you’ll discover that most steps can run in parallel, and the sequential dependencies are few.

Key insight: look for things that “take time but not time in your code” — DB queries, external service calls, user input, file I/O. These are all opportunities to do useful work in parallel instead of blocking.


Topic 34 — Shared State Is Incorrect State (p299)

Tip 57: Shared State Is Incorrect State
Tip 58: Random Failures Are Often Concurrency Issues

Shared mutable state is the root cause of most concurrency bugs. The problem isn’t that two processes can write to the same memory — it’s that neither can guarantee its view of that memory is consistent. Read-check-update sequences are not atomic.

Evolution of solutions:

  1. Semaphores / Mutual Exclusion — one “token” at a time; whoever holds it can access the resource. Works if everyone follows the convention. Problems: forgotten locks, exceptions before unlock.

  2. Transactional resource access — centralize the check-and-take into the resource itself (e.g., get_pie_if_available()). Still requires a semaphore internally; resource manages it.

  3. Multiple resource transactions — acquiring multiple resources atomically is hard. Treat the combination as its own resource, or use a higher-level abstraction.

The deeper message: “Doctor, it hurts when I do this.” “Then don’t do that.”
Managing shared mutable state yourself is fraught. The next sections show how to avoid it entirely.


Topic 35 — Actors and Processes (p309)

Tip 59: Use Actors For Concurrency Without Shared State

The actor model eliminates shared state by design:

  • An actor = an independent virtual processor with private state and a mailbox
  • Actors only interact via one-way messages (no shared memory)
  • No explicit synchronization needed — each actor processes one message at a time
  • Actors can run on one core, many cores, or distributed machines — same code

Properties:

  • No orchestration layer — actors react to messages and send new ones
  • All state is either in messages or local to an actor
  • No replies — if you need a response, include your own mailbox address in the message

Erlang/Elixir are the canonical actor implementations: millions of lightweight processes, supervisor trees for fault tolerance, hot-code loading for near-zero downtime.

Actor model advantages: no locks, no shared memory, naturally concurrent, works on any hardware topology.


Topic 36 — Blackboards (p317)

Tip 60: Use Blackboards to Coordinate Workflow

A blackboard is a shared information space where independent agents post and read facts. No agent needs to know about the others; they all watch for relevant updates and contribute when they can.

Key properties:

  • Agents are completely decoupled — they only know about the blackboard
  • Data arrives in any order; agents react to what’s available
  • New data can trigger new rules/agents
  • Agents can be added/removed without modifying others

Modern equivalent: Kafka, NATS, and other messaging systems with event logs and pattern-matching retrieval function as blackboards. Microservice architectures that communicate via pub/sub are blackboard-like.

Blackboard pattern is ideal when:

  • Data arrives asynchronously and out of order
  • The workflow has many conditional paths based on what’s known
  • Multiple independent agents each have partial knowledge

Cost: harder to reason about, harder to trace. Use a unique trace ID per business function and propagate it through all actor/blackboard messages for debuggability.


Key Takeaways

  1. Temporal coupling (forced ordering) reduces flexibility — analyze workflows for true dependencies and parallelize what can be parallelized.
  2. Shared mutable state is incorrect state — non-atomic read-check-update sequences create race conditions.
  3. Random failures that are hard to reproduce are often concurrency bugs.
  4. The actor model eliminates shared state: isolated actors + one-way messages = no locks needed.
  5. Blackboards coordinate independent agents without them needing to know about each other — ideal for complex, asynchronous workflows.
  6. Modern messaging systems (Kafka, NATS) offer blackboard semantics at scale.