程序语言有其独特的思想。Python 有 Zen of Python,Golang 说 Do not communicate by sharing memory; instead, share memory by communicating。然而,它们并未提升到 worldview 的高度。Joe 老爷子,在其博士论文中,却明确提到了他心目中的一门新语言的 worldview:
everything is a process.
process are strongly isolated.
process creation and destruction is a lightweight operation.
message passing is the only way for processes to interact.
processes have unique names.
if you know the name of a process you can send it a message.
processes share no resources.
error handling is non-local.
processes do what they are supposed to do or fail.
其实它和 OS 的 process 概念几乎一样,就是 PCB(Process Control Block),以及围绕 PCB 的 heap,stack,mailbox 等内存空间。只不过,erlang 的 process 的 memory footprint 很小很小,一个刚初始化的 process,也就占用几百个 words(32bit CPU 下,1 word = 4 bytes,64bit CPU,1 word = 8 bytes)。所以,erlang VM 能创建的 process 理论上取决于内存的大小,我们可以轻易创建百万级,甚至千万级的 process(32bit CPU 的 pid 用 28 bit,64 bit CPU 的 pid 用 60 bit,大家可以自行算算 process 的上限)。erlang 的 process 源自 actor model 的思想,它是几种常见的 concurrency model 的一种(其它的 model 请参考我的文章:
Concurrency
)。process 和 process 通过发送和接收消息来处理各种事务。
erlang 的错误处理机制非常独特 —— error 的 bubble up 是靠 process 之间的 link 完成的。link 是一个作用域双方互相监视的机制,有点像自战国商鞅变法以降,古代中国常有的保甲连坐制度:一人犯法,连坐者也要受刑。两个 link 起来的 process —— 我们暂且赋予他们名称为小明和小红 —— 如果小明挂了,小红会收到
EXIT
signal,如若不作处理,正常情况下也会挂掉,而小红之死,会进一步触发和小红 link 起来的其他 processes,从而引发和小明有关的整条利益链上的震荡。这便是著名的 let it crash 思想。这种独特的机制让 error handling 实现了真正意义上的 non-local:一切与其相关的 process,都会受其影响,就像是对待癌细胞一样,除非有对症的法子,否则相关的细胞统统杀死,绝不姑息。反观其他语言的 error handling,与其说是 handling,不如说是 hide —— 就像边关报急,烽火连天,兵部却将其压下,回报圣上天下太平,然后马照跑,舞照跳。
link 是双向的连坐机制,有时候对于一些影响不大的问题,我们并不需要如此强有力的手段,于是有了 monitor。monitor 像是战场上的斥候,监视一个 process 的异动,一旦有变,立即回禀一个消息。收到消息的 process 可以选择按兵不动,或者随机应变。
erlang/OTP 的 supervision tree 就是基于 link/monitor 这样非常简单的机制构建起来的。而之所以 erlang 可以任性的 let it crash,其中一个很大的原因是 process 非常轻量,构建和销毁的代价无非就是创建和回收若干个 C structs。
这些问题我自个也未完全明白(尤其是 link —— 到目前为止,我连它的 source code 在哪都不知道),不过,带着问题学习总是好的。
先说 message 的传递。我们且将 distributed erlang 放在一边,只考虑 single node。在 erlang VM 里,小明发个 message 给小红,其实就是把 message body 从小明的 heap 里拷贝到小红的 heap 里(或者 heap fragment 里,这个区别暂时不需要关心),然后往小红的 mailbox 尾部添加一个指向拷贝后的 message body 的 message control block。如果小红是网红,同一时刻小刚也可能给小红发 message,那么小明就要和小刚竞争,这个时候就需要锁。这把锁是 per process 的,所以很轻。
了解了这个过程,我们就回答了前两个问题。message 不会在传递的过程中丢失,而且这是 exactly once 的 message passing 的场景(我之前的文章
ZeroMQ及其模式
中讲到了分布式系统 exactly once 是不可能的,想想看,这里为什么可以?)。如果小红对小明不理不睬,小明对小红不离不弃,矢志不渝地发消息,会发生什么事?小红的 mailbox 会一直增长下去,直到海枯石烂 —— 或者说内存耗尽。这时 VM crash,所有 process 一起玩完。