CS PAPERS DEEP-READ · PAPER 66

Communicating Sequential Processes (CSP)

C. A. R. Hoare · Communications of the ACM · 1978

中文 →

What did this paper do?

In 1978 Tony Hoare proposed CSP: when a program has to do several things at once, don't let those things share one pot of memory and then fence each other off; let each run on its own and have them deal with each other only by handing over messages. This is not a paper with experimental numbers — what it offers is a way of writing programs. When you write Go channels today, or run telecom-grade systems on Erlang / Elixir, you are using this paper's worldview.

The pain of the old world

The standard answer back then: several simultaneous strands of work share one pot of memory, everyone can read it and everyone can change it; to keep them from colliding, you agree on a separate set of rules — raise your hand first, and only then may you touch it.

Picture a roomful of people sharing one whiteboard that says how much money is in the account. There are two kinds of trouble. First, break the rule once and the board gets scribbled over: one person works out "100 minus 30" and is about to write it down, but someone else has already changed the number to 80 — and what lands on the board is 70. Second, and far more maddening: the error does not reproduce. Run it a hundred times and only the third run is wrong, because whether it breaks depends on whether two people happened to reach out in the very same microsecond.

The idea

Hoare's move is: take the whiteboard away.

Give each person their own small room and their own sheet of paper, where nobody can see anyone else's sheet; to exchange anything, you may only pass a note through the window. That class of bug is then not policed more strictly — it becomes impossible to have: with nothing shared, there is nothing to fight over, and since only one person ever touches a given sheet, the arithmetic on that sheet is always consistent.

How does passing a note also get the timing right?

The key is that it is hand to hand: the person offering the note must hold their arm out and wait for someone to take it, and the person reaching for one must wait for someone to offer it; whoever arrives first waits for the other, and they let go at the same instant, then each goes back to their own work.

That removes the old world's second mechanism: you used to write the data on the board and then separately announce "hey, it's ready" — and the bugs lived precisely in the mismatch between the two. Hand to hand, the note arriving is the announcement.

The second point matters just as much: one person can watch several windows at once and serve whichever window a note comes through first — you don't name in advance who you are waiting for, only that you're waiting for whoever is first. That is what lets one person act as a service window, taking several people in turn; without it you can only write a rigid single-file pipeline.

Those two moves are enough to build everything a language used to have to provide built in. Want a buffer that can hold goods? Hire someone as a runner who takes deliveries from the front and hands them out at the back. Want "only three people in the kitchen at a time"? Hire a doorkeeper who counts heads. What used to be built-in features all become "hire one more person".

What it made possible

In the 1980s people really did build a language from it (occam), and even a chip (the Transputer) with "process" and "window" built straight into the hardware — a paper turning into a CPU is rare. Go's channels, Erlang's message passing, even the microservice instinct that everything should guard its own data and expose only an interface, all come from the same source.

One honest caveat: this trick abolishes "the board got scribbled over", but it does not abolish everyone waiting on everyone else — if two people both hold their arms out waiting for the other to hand over first, they seize up together. Hoare's own five-philosophers-at-dinner example needs that extra doorkeeper precisely so it doesn't jam.

The one-line takeaway

Don't let parallel strands of work share one pot of memory and fence each other off with locks; give each its own and let them pass messages hand to hand — the instant the note lands, handing over the data and lining up the timing are both done, so data collisions cannot happen. Then let a process wait on "whichever of these windows comes first", and buffers, permits and pipelines can all be written out rather than built in. The cost: it cures data collisions, not mutual waiting.

Want the rendezvous timing diagram, the guarded-command notation, and how it grew into Go's channels and select? → switch to the deep read