Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Concurrency

Concept

A foundational idea to recognize and understand.

“Concurrency is not parallelism.” — Rob Pike

Understand This First

  • Algorithmic Complexity – understanding the cost of operations helps you decide what is worth parallelizing.

Context

At the architectural level, modern software almost never does just one thing at a time. A web server handles hundreds of requests simultaneously. A mobile app fetches data from a network while keeping the interface responsive. An AI agent calls multiple tools and waits for results while continuing to plan. Concurrency is the practice of managing multiple activities whose execution overlaps in time.

Concurrency is distinct from parallelism, though the two are often confused. Parallelism means multiple computations literally running at the same instant (on multiple CPU cores). Concurrency means multiple activities are in progress at the same time, even if only one is actively executing at any given moment, like a chef alternating between chopping vegetables and stirring a pot. Concurrency is about structure; parallelism is about execution.

Problem

Your system needs to handle multiple tasks that overlap in time: serving many users, processing a queue of jobs, or coordinating several I/O operations. But those tasks may share data, compete for resources, or depend on each other’s results. How do you structure the work so that tasks make progress without corrupting shared state or deadlocking?

Forces

  • Responsiveness vs. complexity: Users expect fast, responsive software, but concurrent code is harder to write, test, and debug than sequential code.
  • Throughput vs. correctness: Doing more work simultaneously increases throughput, but shared mutable state introduces race conditions, bugs that appear only under specific timing.
  • Resource utilization vs. contention: Concurrency lets you use idle resources (waiting for I/O? do something else), but too many concurrent tasks competing for the same resource creates bottlenecks.
  • Simplicity vs. performance: Sequential code is easy to reason about but wastes time waiting. Concurrent code is efficient but introduces an entire class of subtle bugs.

Solution

Choose a concurrency model that fits your problem, and use the tools your platform provides to manage shared state safely.

The most common models are:

Threads with locks. Multiple threads of execution share memory. When they need to access shared data, they use locks (mutexes) to ensure only one thread accesses the data at a time. This is the traditional model and the most error-prone. Forgotten locks cause race conditions, and overly aggressive locking causes deadlocks.

Message passing. Instead of sharing memory, concurrent tasks communicate by sending messages to each other through channels or queues. Each task owns its own data. This model avoids most shared-state bugs but requires careful design of the message flow.

Async/await. A single thread handles many tasks by switching between them at explicit suspension points (typically I/O operations). This is common in JavaScript, Python, and Rust. It avoids many threading bugs but introduces its own complexity around when and where suspension happens.

Actors. Each actor is an independent unit with its own state that processes messages sequentially. Concurrency comes from having many actors running simultaneously. Popular in Erlang/Elixir and the Akka framework.

The right choice depends on your problem. I/O-heavy work (web servers, API clients) often benefits from async/await. CPU-heavy parallel computation benefits from threads or processes. Distributed systems often use message passing or actors.

How It Plays Out

An AI agent is asked to build a web scraper that fetches data from a hundred URLs. A sequential approach (fetch one, then the next) takes minutes. The agent restructures the code to use async/await, launching all fetches concurrently and collecting results as they arrive. The same work finishes in seconds.

Tip

When asking an AI agent to write concurrent code, specify the concurrency model you want (async/await, threads, etc.) and whether shared mutable state is acceptable. Left to its own devices, the agent may choose a model that is correct but inappropriate for your platform or performance requirements.

A team discovers that their application occasionally produces corrupted data. The bug is intermittent and impossible to reproduce reliably. After weeks of investigation, they find that two threads write to the same data structure without synchronization. The bug only manifests when both threads happen to write at the exact same moment, a classic race condition. The fix is adding proper synchronization, but the real lesson is that concurrent access to shared mutable state must be designed for, not discovered after the fact.

Example Prompt

“Rewrite the URL fetcher to use async/await so all 100 requests run concurrently. Add a semaphore to limit concurrent connections to 20. Make sure errors on individual requests don’t crash the whole batch.”

Consequences

Concurrency enables responsive, high-throughput systems that use resources efficiently. Without it, modern software (web applications, mobile apps, data pipelines) would be unacceptably slow.

The cost is a permanent increase in complexity. Concurrent bugs (race conditions, deadlocks, livelocks) are among the hardest to find and fix because they depend on timing, which is nondeterministic (see Determinism). Testing concurrent code requires specialized techniques. And reasoning about concurrent systems means thinking about interleavings, the many possible orderings in which operations might occur, which grows combinatorially with the number of concurrent activities.

  • Contrasts with: Determinism — concurrency introduces nondeterminism through scheduling, even when individual tasks are deterministic.
  • Uses: Protocol — concurrent systems that communicate need protocols to coordinate safely.
  • Uses: Event — event-driven architectures are a common way to structure concurrent systems.
  • Depends on: Algorithmic Complexity — understanding the cost of operations helps you decide what is worth parallelizing.
  • Refined by: Side Effect — minimizing shared side effects is the most effective way to reduce concurrency bugs.