锁与虚位占用
分布式房间加入的一次工程性选择
在多人实时系统中,“加入房间”是一个看似简单、但极易出问题的流程。
真正的复杂度,往往隐藏在并发与分布式之下。
本文从一个非常具体的问题出发,聊一聊我在 Skynet 架构中,为什么最终选择了虚位占用,而不是传统的锁。
一、问题从哪里开始?
当一个玩家加入一个尚未满员的房间时,系统中一定会经历一个阶段:
已发起加入,但尚未真正完成加入
这个阶段,就是典型的中间态。
如果我们无法正确管理这个中间态,就会产生数据不一致的问题,比如:
- 房间看起来“还有位置”,但实际上已经被占用
- 多个玩家同时被允许加入,最终超员
- 管理服务和房间服务看到的状态不一致
中间态存在的时间越长,出问题的概率就越高。
二、并发 + 分布式,让问题放大
再看一个更真实的工程背景:
- 可能同时有多个玩家尝试加入同一个房间
- 房间管理服务 和 房间服务本身 分布在 不同节点
- 玩家加入房间的入口逻辑在管理服务上
- 真正的房间数据,却在房间服务节点
这意味着:
你无法假设所有操作都在一个进程、一个内存、一个时序中完成。
三、第一反应:加锁,真的合适吗?
为了解决并发修改问题,最直观的方案是:
在加入房间时,对房间数据加锁
在 Skynet 中,通常意味着使用 skynet.queue。
但问题在于:
️ 锁的粒度很难控制
是锁整个房间?
还是锁某个成员列表?
锁会引入等待和排队
在高并发场景下,锁本身会变成性能瓶颈
Skynet 的设计哲学并不鼓励滥用锁
Skynet 更强调:
- Actor 隔离
- 消息驱动
- 状态前移,而不是互斥等待
在 Skynet 中,锁是“能不用就不用”的工具。
四、换个角度:问题真的一定要用锁解决吗?
回到最初的问题:
我们真正想解决的,其实只有一件事
“在加入完成之前,防止其他人再抢这个位置”
那是否可以:
- 不等待
- 不阻塞
- 不加锁
- 但仍然保证“位置不会被重复使用”?
答案就是:虚位占用。
五、什么是虚位占用?
虚位占用的核心思想只有一句话:
先占位置,再做后续流程
具体做法是:
- 当玩家发起加入请求
- 管理服务立即为房间创建一个“虚位”
- 虚位计入房间人数
- 后续加入请求看到房间已满,自然失败或排队
- 加入流程完成后:
- 虚位 → 实位
- 若流程失败:
- 释放虚位
六、虚位占用解决了什么问题?
消灭中间态的不确定性
位置一旦被虚占,就对外“不可见”。
天然支持并发
多个请求同时到达,也只会有一个成功占位。
无需锁
不阻塞、不等待、不排队。
更符合 Skynet 的消息模型
状态前移,逻辑简单,失败路径清晰。
七、锁 vs 虚位,占用不是“技巧”,而是思维转变
| 方案 | 思路 | 成本 | 风险 |
|---|---|---|---|
| 锁 | 等别人操作完 | 高 | 死锁、性能下降 |
| 虚位 | 先声明资源归属 | 低 | 需要设计好回收逻辑 |
虚位占用不是取巧,而是把“并发控制”从“等待”变成了“声明”。
八、结语
在分布式系统中,
真正可靠的方案,往往不是“控制别人不要做什么”,
而是:
让状态本身,不再给错误留下空间。
如果你也在 Skynet 或类似 Actor 模型中处理并发问题,
不妨试试从“锁”的视角,切换到“虚位”的视角。
很多复杂问题,都会一下子变简单。
本文基于真实工程经验总结,如有不同实现方式,欢迎交流。
--完--
- 原文作者: 留白
- 原文链接: https://zfunnily.github.io/1/01/12/
- 更新时间:2026-08-08 01:40:44
- 本文声明:转载请标记原文作者及链接