IT 论文精读 · PAPER 66

Communicating Sequential Processes(CSP)

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

EN →

这篇论文干了什么?

1978 年,Tony Hoare 提出了 CSP:让一个程序同时做好几件事,不要让这几件事共用同一块内存、再互相拦着;而要让它们各干各的、彼此之间只靠递消息打交道。这不是一篇有实验数字的论文,它给的是一套写法。今天你写 Go 的 channel、用 Erlang / Elixir 撑电信级系统,背后的世界观都是这一篇。

先说说旧世界的痛

当时的标准答案是:几条同时在跑的活儿共用一块内存,谁都能读、谁都能改;为了不打架,另外再约一套规则——谁先举手谁才能动。

想象一屋子人共用一块白板,上面写着账上还有多少钱。麻烦有两层:一是规则一破白板就被写花——一个人把「100 减 30」算完刚要落笔,另一个已经把数改成了 80,最后落在板上的却是 70。二是更折磨人的:这种错不复现,跑一百次只有第三次出错,因为出不出错取决于两人是否恰好在同一微秒伸手。

那个点子

Hoare 的做法是:把白板拆掉。

每人一个小房间,自己一张纸,谁也看不见别人的纸;要交换信息,只能从窗口把纸条递过去。于是那类 bug 不是被管得更严,而是无从发生——没有共享的东西,就没有可抢的东西;每张纸只有一个人碰,纸上的账永远自洽。

递纸条怎么就顺带把时间对上了?

关键在于手递手:递的人把手伸出窗口就得停下来等对方来接,接的人伸手也得等对方来递;先到的等后到的,碰上了才同时松手,然后各自回去接着干。

这就省掉了旧世界的另一套机制:以前你得先把数据写上白板、再另外通知一声「喂,我写好了」,bug 正出在两者错配;手递手之后,纸条递到了就等于通知到了。

第二件事同样要紧:一个人可以同时守着几个窗口,哪个窗口先伸进纸条就先处理哪个——不预先规定等谁,只等「谁先来」。有了这条,一个人才能当服务窗口轮流招呼好几个人;没有它,你只能写死板的单线流水线。

就这两下,够搭出原先要语言内置的一切:想要个能攒货的缓冲区,就请一个人当传递员,前面来货就收、后面要货就给;想要「同时只准三个人进厨房」,就请个门房数人头。以前的内置功能,这里全变成「多请一个人」。

带来了什么

1980 年代真有人照它造了一门语言(occam),还造了一款把「进程」和「窗口」直接做进硬件的芯片(Transputer)——论文变成 CPU,这很少见。今天 Go 的 channel、Erlang 的消息传递,以至「各自守着自己的数据、只走接口」的微服务直觉,都同出一源。

一句诚实话:这招消灭了「白板被写花」,却没消灭「互相干等」——两个人都伸着手等对方先递,就一起僵在那儿。Hoare 那个五位哲学家吃饭的例子,正是得额外请个门房才不卡住。

一句话记住

别让并行的几件活共用一块内存、再拿锁互相拦;让它们各自私有、只靠「手递手」递消息——递到的那一刻,交数据和对时间一次完成,数据打架于是无从发生。再允许一个人「等几个窗口里谁先来」,缓冲区、许可牌、流水线就都能自己写出来。代价是:它治数据打架,不治互相干等。

想看会合的时序图、守卫命令的写法,以及它怎么长成 Go 的 channel 与 select? → 切到精读版