模块 05查看课程目录
05
并发中级ElixirErlangBEAM
从裸进程理解并发
先亲手写消息循环,才知道 GenServer 替你处理了什么。
4检查点
6 小时预计完成
为什么先学这个
先修正心智模型,再增加 API
直接从 GenServer 开始,容易把 callback 当作框架魔法。裸进程暴露了关键事实:call 只是带引用的消息与等待回复;状态只是循环参数;超时、崩溃和陈旧回复都必须进入协议设计。
学完你能做到
可验证的学习目标
- 能设计带唯一引用的 request/reply 协议
- 能解释 link 与 monitor 的不同故障语义
- 能识别同步死锁、陈旧回复与 mailbox 堆积
前置知识
- 掌握两种语言的模式匹配
- 理解 BEAM mailbox
最小心智模型
先抓住三个词
reference
VM 生成的高概率唯一值,常用于把某个 reply 与对应 request 配对。
link
双向故障关系。默认情况下,一个非 normal exit 会沿 link 传播。
monitor
单向观察关系。被观察进程退出时,观察者收到 `DOWN` 消息而不会自动退出。
双语代码桥
先对齐协议,再看标点
唯一引用避免把旧回复当成新请求的结果;timeout 只停止等待,并不会撤回已经发送的消息。
Elixir
def call(server, request, timeout \\ 1_000) do
ref = make_ref()
send(server, {:call, self(), ref, request})
receive do
{:reply, ^ref, response} -> {:ok, response}
after
timeout -> {:error, :timeout}
end
endErlang
call(Server, Request, Timeout) ->
Ref = make_ref(),
Server ! {call, self(), Ref, Request},
receive
{reply, Ref, Response} -> {ok, Response}
after Timeout ->
{error, timeout}
end.LAB
约 15–25 分钟可运行实验
制造一条过期回复
让服务器睡眠 1500ms,客户端 500ms 超时。随后检查客户端 mailbox:你会看到超时之后到达的 reply。
- 01
实现一个收到请求后延迟回复的 server
- 02
以 500ms timeout 发起 call
- 03
等待两秒后运行 `Process.info(self(), :messages)`
在终端 / shell 中运行
Process.info(self(), :messages)预期观察
- call 返回 `{:error, :timeout}`
- 迟到的 reply 仍可能出现在调用者 mailbox
故意弄坏
删除 ref,只按 `{:reply, response}` 匹配。连续发送两个不同延迟的请求,观察回复如何可能错配。
这个实验能证明
客户端超时与服务端工作取消是两件不同的事。
这个实验不能证明
一次错配演示不能覆盖所有调度顺序,也不能证明固定 timeout 适合生产流量。
快速自测
客户端 timeout 后,已经发给服务端的消息会怎样?
本章挑战
手写可靠 KV 进程
实现 get/put/delete,所有同步请求带 ref,支持 timeout;再提供 monitor 版本让调用者能区分超时与服务端崩溃。
提示 1轻推一下
先写消息协议,再写 loop。
提示 2缩小问题
把 `DOWN` 与 reply 放进同一个 receive。
提示 3接近实现
思考 monitor 在正常回复后何时 demonitor。
提示 4用验收标准反推
从下面每一条验收标准倒推一个最小测试。若某条无法写成测试,先把表述改成可观察结果。
完成标准
- 并发请求不会因回复顺序不同而错配
- 调用方能区分 timeout 与服务端退出
- 测试覆盖迟到回复
带走这三句话
复习卡
- 1timeout 不是取消。
- 2reference 是 request/reply 配对的核心工具。
- 3link 用于共同生死,monitor 用于观察并做决定。
继续核对