skip to content

questions

6

What is the CSP (Communicating Sequential Processes) model of concurrency, and how does coordinating tasks by passing values over channels differ from coordinating them with shared mutable state and locks?

level: juniorimportance: must knowfreq 50%

answer

  1. processes own state, channels carry values
  2. communication is the synchronization
  3. hazard surface: every field vs one handoff
  4. channels are first-class, topology = program shape
  5. races gone, deadlock still possible

basics

~20 s

CSP models a program as independent sequential processes that share no memory and interact only by sending and receiving values on channels. The communication is itself the synchronization, so there is no shared mutable state to lock.

solid answer

~50 s

CSP is a model where a concurrent program is a set of sequential processes that own their state and interact only by passing values on channels. Each process is ordinary straight-line code internally; all concurrency lives at the channel operations. Versus shared state plus locks: with locks any thread can touch any shared location, so correctness depends on every access taking the right lock in the right order and the hazard surface is every field. With channels a value is handed over at one defined point, and the channel provides both the transfer and the ordering, so there is one place to get right. Channels are first-class values: you create them, pass them around, and hand a process the channels it should talk on. That makes CSP compositional — pipelines, fan-out and fan-in are just channel topologies. CSP removes data races by construction. It does not remove deadlock, and the guarantee only holds if you send data rather than references into state you keep mutating.

code

text · 14 lines
text
# shared state: every writer must remember the lock
lock.acquire(); count = count + 1; lock.release()

# CSP: one process owns count; others send it messages
process Counter(requests):
    count = 0
    loop:
        msg = receive(requests)
        if msg is INC: count = count + 1
        if msg is GET: send(msg.replyChannel, count)

# caller
send(requests, INC)
send(requests, GET(reply)); value = receive(reply)

go deeper

for a junior

Say what a channel is, that processes do not share memory, and that the send/receive is the coordination point. One concrete pipeline example is enough.

for a middle

Contrast the hazard surfaces: every field under locks versus one handoff under channels, and note that ordering comes free with the channel operation.

for a senior

Point out the leak in practice — sending a reference reintroduces shared memory — and that channel programs trade races for deadlock and for per-message overhead.

for a principal

Frame it as choosing where concurrency is expressed: CSP makes the channel graph the architecture, which is reviewable and testable, at the cost of message overhead and a new failure mode set (blocked partners, unbounded queues, shutdown protocols).

## The model CSP describes a concurrent system as a set of sequential processes. Process here is a logical unit of execution, not necessarily an OS process: a thread, a green thread, a coroutine or a task. Two rules define the model. 1. A process owns its state. No other process can read or write it. 2. Processes interact only by communication events on channels: one process sends a value, another receives it. Because the code inside a process is sequential, you can read it top to bottom like single-threaded code. Interleaving only matters at the send and receive points, and those are visible in the source. ## Why this differs from locks Under shared memory the unit of sharing is a memory location. Any thread that has a reference can read or write it, so every field is a potential hazard and correctness is a global property of the whole program: one function that forgets the lock breaks code it never calls. Compilers and CPUs also reorder memory operations, so you additionally need ordering rules to make writes visible. Under CSP the unit of sharing is a message. A value crosses the boundary at exactly one point, and the channel implementation supplies the ordering edge, so user code never reasons about visibility. The reviewable question shrinks from what does every thread do to what messages does this process send and receive, and in what order. This is why CSP-style code is often described as making concurrency structural: the shape of the program is the shape of the channel graph. ## Channels are first class A channel is a typed conduit that can be created, stored, and passed as a value, including over other channels. That has practical consequences: a request can carry a reply channel so the responder does not need to know who asked; a supervisor can hand a worker its input and output channels and thereby wire an arbitrary topology; a pipeline stage can be replaced by rewiring rather than rewriting. In the actor style the addressee is a named mailbox instead, so the sender must know the recipient; CSP inverts that and names the conduit. ## What you get and what you do not You get: no data races on the transferred value, one synchronization vocabulary instead of locks plus condition variables plus flags, natural flow control because a bounded channel blocks a sender that runs too far ahead, and testability, since a stage can be tested by feeding channels. You do not get freedom from deadlock. Processes still block waiting for partners, and a cycle of such waits hangs exactly like a lock cycle. You do not automatically get performance either: every message has a cost, and a fine-grained channel between two stages that could have been one function is pure overhead. And crucially, CSP guarantees hold only if the value you send is not a live window into your own mutable state. Most languages let you send a reference; if the sender keeps using the object afterwards, shared memory is back with none of the protection. ## Parallelism Sequential in the name describes each process internally, not the system. Many processes run concurrently and the runtime maps them onto threads and cores, so a CSP program parallelizes naturally: fan a stage out to N copies reading the same channel and the work is distributed with no scheduler of your own.

  • Does using channels make data races impossible in any language?
    Only if the message is a value the sender stops touching. Most languages let you send a pointer or object reference, and if the sender keeps mutating it after the send, two tasks are back to sharing memory with no lock. Pure message-passing languages avoid this by copying the message or by moving ownership so the sender's reference becomes unusable.
  • Each process is sequential, so where does parallel speedup come from?
    From running many processes at once. The runtime schedules them across threads and cores; sequential describes only the code inside one process. To parallelize a bottleneck stage you run N copies of it reading from the same input channel, which distributes work automatically because whichever copy is idle receives next.

Locks are an open-plan office where anyone can edit anyone's document if they remember to grab the right pen. CSP is a set of closed offices connected by pneumatic tubes: you never touch someone else's desk, you send them a tube.

saying these in an interview costs you the question

  • Saying CSP eliminates deadlock — it removes data races, not blocked-waiting cycles.
  • Treating a channel as merely a thread-safe queue and missing that it also provides the ordering and blocking semantics.
  • Assuming processes in CSP means OS processes rather than any sequential unit of execution.
  • Claiming sending is always a copy, then keeping and mutating the object you just sent.

context

open as a page

In a channel-based concurrency model, what is the difference between an unbuffered (rendezvous) channel and a buffered one, and what does each guarantee about when the sender resumes?

level: middleimportance: must knowfreq 55%

basics

~20 s

An unbuffered channel is a rendezvous: the send completes only when a receiver takes the value, so both sides synchronize. A buffered channel lets the sender resume as soon as the value fits in the buffer, and blocks only when it is full.

open as a page

A program uses channels exclusively and holds no locks at all, yet it hangs. What kinds of channel usage cause that, and how do you diagnose and prevent it?

level: seniorimportance: must knowfreq 45%

basics

~20 s

Channel operations block, so tasks can wait in a cycle just like lock holders do. Typical causes: a send with no receiver ever arriving, a receive with no sender, two tasks sending to each other over unbuffered channels, and a loop waiting for a stream nobody closes.

open as a page

When a task must wait on several channels at once, what does a select (multi-way choice) construct do, and what happens if more than one channel is ready at the same moment?

level: middleimportance: should knowfreq 42%

basics

~20 s

Select waits on several communication operations at once and proceeds with whichever becomes ready, running only that branch. If several are ready it picks one nondeterministically, usually at random for fairness. An optional default branch makes it a non-blocking poll.

open as a page

In a channel-based pipeline, how should closing a channel work: who is allowed to close it, what do receivers observe afterwards, and what breaks when several producers share one channel?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Closing broadcasts end-of-stream. Receivers drain any buffered values first, then every receive reports closed immediately. Only the sending side may close, and with several producers you need a coordinator that closes once all of them have finished, never each producer closing.

open as a page

One stage of a channel pipeline is the bottleneck. How would you fan the work out to parallel workers and fan the results back into a single stream, and what must you decide about ordering, buffer sizes, and shutdown?

level: principalimportance: should knowfreq 33%

basics

~20 s

Fan-out: run N copies of the stage all receiving from the same input channel, so idle workers naturally take the next item. Fan-in: a merger that forwards each worker's output into one channel and closes it after all workers finish. Fan-out loses ordering; keep buffers small and give every worker a cancel path.

open as a page