模块 01查看课程目录
01
地基零基础BEAMOTP
先建立 BEAM 心智模型
不可变数据、轻量进程与故障隔离,是后续所有章节的坐标系。
3检查点
3 小时预计完成
为什么先学这个
先修正心智模型,再增加 API
如果把 BEAM 进程等同于操作系统进程,就会高估创建成本;如果把它等同于共享内存线程,又会错误地寻找锁。BEAM 的选择是:大量隔离的小进程,以消息通信,让局部失败可以被观察、重启和限制。
学完你能做到
可验证的学习目标
- 能区分 BEAM 进程、OS 进程和线程
- 能解释 mailbox、独立堆和抢占式调度之间的关系
- 能预测不可变更新和模式匹配的结果
前置知识
- 完成起跑线
- 见过 tuple、list、map
最小心智模型
先抓住三个词
轻量进程
由 BEAM 调度的隔离执行单元。它不是 OS 进程,创建和切换成本都更低。
mailbox
每个进程自己的消息队列。发送是异步的,接收方通过模式选择下一条可处理消息。
reduction
BEAM 衡量进程工作量的近似单位。调度器用它避免单个进程长期霸占执行权。
双语代码桥
先对齐协议,再看标点
状态不是被原地修改;每次收到消息,进程都以一个新参数进入下一轮递归。
Elixir
counter = fn loop, total ->
receive do
{:add, n} -> loop.(loop, total + n)
{:read, caller} ->
send(caller, {:total, total})
loop.(loop, total)
end
end
pid = spawn(fn -> counter.(counter, 0) end)Erlang
Counter = fun Loop(Total) ->
receive
{add, N} -> Loop(Total + N);
{read, Caller} ->
Caller ! {total, Total},
Loop(Total)
end
end,
Pid = spawn(fun() -> Counter(0) end).LAB
约 15–25 分钟可运行实验
看见 mailbox,而不是想象共享变量
启动计数器,连续发送三条消息,再询问状态。重点不是答案 6,而是状态如何只存在于那个进程的下一次递归参数里。
- 01
向进程连续发送 `{:add, 1}`、`{:add, 2}`、`{:add, 3}`
- 02
发送带自己 PID 的 `{:read, self()}`
- 03
用 `Process.info(pid, :message_queue_len)` 观察队列长度
在终端 / shell 中运行
send(pid, {:add, 1}); send(pid, {:add, 2}); send(pid, {:add, 3})预期观察
- 最终回复为 `{:total, 6}`
- 计数器没有暴露可被其他进程直接修改的内存地址
故意弄坏
删除 `{:read, caller}` 分支,再发送读取消息。进程不会自动报错;消息会留在 mailbox 中。
这个实验能证明
这个递归循环可以通过消息串行化自己的状态更新。
这个实验不能证明
它不能证明系统没有竞态,也不能证明任意 mailbox 都不会无限增长。
快速自测
一个进程收到无法匹配当前 receive 子句的消息时,通常会怎样?
本章挑战
画出三进程天气站
只用纸或文本画出 collector、sensor、dashboard 三个进程和它们的消息协议,并标明每条消息由谁创建、谁消费。
提示 1轻推一下
消息名称应表达业务含义,不要只写 `data`。
提示 2缩小问题
查询类消息需要返回地址或引用。
提示 3接近实现
考虑 dashboard 暂时离线时,collector 的 mailbox 会发生什么。
提示 4用验收标准反推
从下面每一条验收标准倒推一个最小测试。若某条无法写成测试,先把表述改成可观察结果。
完成标准
- 每条消息都有明确发送者与接收者
- 状态归属到具体进程
- 至少指出一个队列可能增长的位置
带走这三句话
复习卡
- 1BEAM 进程通过隔离降低共享状态的复杂度,但不会自动消灭协议错误。
- 2递归参数是进程状态的一种常见表达。
- 3无法匹配的消息会留下来;mailbox 长度是重要运行指标。
继续核对