锁与虚位占用

分布式房间加入的一次工程性选择

在多人实时系统中,“加入房间”是一个看似简单、但极易出问题的流程。
真正的复杂度,往往隐藏在并发与分布式之下。

本文从一个非常具体的问题出发,聊一聊我在 Skynet 架构中,为什么最终选择了虚位占用,而不是传统的


一、问题从哪里开始?

当一个玩家加入一个尚未满员的房间时,系统中一定会经历一个阶段:

已发起加入,但尚未真正完成加入

这个阶段,就是典型的中间态

如果我们无法正确管理这个中间态,就会产生数据不一致的问题,比如:

  • 房间看起来“还有位置”,但实际上已经被占用
  • 多个玩家同时被允许加入,最终超员
  • 管理服务和房间服务看到的状态不一致

中间态存在的时间越长,出问题的概率就越高。


二、并发 + 分布式,让问题放大

再看一个更真实的工程背景:

  • 可能同时有多个玩家尝试加入同一个房间
  • 房间管理服务房间服务本身 分布在 不同节点
  • 玩家加入房间的入口逻辑在管理服务上
  • 真正的房间数据,却在房间服务节点

这意味着:

你无法假设所有操作都在一个进程、一个内存、一个时序中完成。


三、第一反应:加锁,真的合适吗?

为了解决并发修改问题,最直观的方案是:

在加入房间时,对房间数据加锁

在 Skynet 中,通常意味着使用 skynet.queue

但问题在于:

️ 锁的粒度很难控制

是锁整个房间?
还是锁某个成员列表?

锁会引入等待和排队

在高并发场景下,锁本身会变成性能瓶颈

Skynet 的设计哲学并不鼓励滥用锁

Skynet 更强调:

  • Actor 隔离
  • 消息驱动
  • 状态前移,而不是互斥等待

在 Skynet 中,锁是“能不用就不用”的工具。


四、换个角度:问题真的一定要用锁解决吗?

回到最初的问题:

我们真正想解决的,其实只有一件事
“在加入完成之前,防止其他人再抢这个位置”

那是否可以:

  • 不等待
  • 不阻塞
  • 不加锁
  • 但仍然保证“位置不会被重复使用”?

答案就是:虚位占用


五、什么是虚位占用?

虚位占用的核心思想只有一句话:

先占位置,再做后续流程

具体做法是:

  1. 当玩家发起加入请求
  2. 管理服务立即为房间创建一个“虚位”
  3. 虚位计入房间人数
  4. 后续加入请求看到房间已满,自然失败或排队
  5. 加入流程完成后:
    • 虚位 → 实位
  6. 若流程失败:
    • 释放虚位

六、虚位占用解决了什么问题?

消灭中间态的不确定性

位置一旦被虚占,就对外“不可见”。

天然支持并发

多个请求同时到达,也只会有一个成功占位。

无需锁

不阻塞、不等待、不排队。

更符合 Skynet 的消息模型

状态前移,逻辑简单,失败路径清晰。


七、锁 vs 虚位,占用不是“技巧”,而是思维转变

方案思路成本风险
等别人操作完死锁、性能下降
虚位先声明资源归属需要设计好回收逻辑

虚位占用不是取巧,而是把“并发控制”从“等待”变成了“声明”。


八、结语

在分布式系统中,
真正可靠的方案,往往不是“控制别人不要做什么”,
而是:

让状态本身,不再给错误留下空间。

如果你也在 Skynet 或类似 Actor 模型中处理并发问题,
不妨试试从“锁”的视角,切换到“虚位”的视角。

很多复杂问题,都会一下子变简单。


本文基于真实工程经验总结,如有不同实现方式,欢迎交流。

--完--