103 lines
4.8 KiB
ReStructuredText
103 lines
4.8 KiB
ReStructuredText
.. SPDX-License-Identifier: GFDL-1.3-or-later
|
||
|
||
关于Fk在同一个Lua中运行多个游戏房间的思考
|
||
=========================================
|
||
|
||
目前Fk的实现中,每个游戏房间是一根线程,这就出现了一个问题:
|
||
当同时游戏的房间很多的时候,会给RAM带来相当大的负担,毕竟lua_State太多了,
|
||
每个Room都有一个。 目前比较流行的模式是只需三人就可进行的斗地主,
|
||
有时候同时运行着30多桌斗地主,直接把内存推向1GB占用,云服务器那才多少点资源。
|
||
|
||
如果这么多房间都在同一个Lua运行的话,情况应该会有很大改观。
|
||
|
||
多个房间如何调度?
|
||
------------------
|
||
|
||
在当前阶段已经实现了重连和观战机制,这是借助于协程机制实现的。
|
||
当Lua正在执行delay或者等待用户答复时,Lua会从主协程中切换出来,
|
||
然后去另一个协程处理诸如旁观、重连等游戏逻辑之外的请求。
|
||
|
||
如果把这种空白时间更加充分的利用起来的话,或许就能同时执行多个房间了。
|
||
|
||
现在每个游戏房间在Lua中都是一个Room对象的实例,而Room
|
||
本身是通过一个协程执行着主游戏逻辑。
|
||
考虑修改一下,使得同一个Lua中能执行多个Room协程。
|
||
|
||
暂且考虑开个数组保存所有正在运行中的房间。
|
||
|
||
就绪队列
|
||
--------
|
||
|
||
调度器除了维护运行中房间的数组之外,还维护一个就绪队列。
|
||
|
||
当房间不处于阻塞状态时,或者已经可以脱离阻塞状态,那么就认为他就绪。
|
||
|
||
此外还有一个特殊的协程,他用来处理托管、旁观等请求。
|
||
当他的这个请求队列为空的时候,也视为他未就绪。
|
||
|
||
.. note::
|
||
|
||
所谓阻塞状态目前指的是正在delay或者正在等候答复时。自然而然的,
|
||
脱离阻塞状态就是delay的时间已经结束或者已收到答复。
|
||
|
||
当所有房间全部未就绪时,调度器调用sleep睡一段时间,让出CPU。
|
||
|
||
当房间因为delay而延时时,可以知道他恢复就绪的确切等待时间。但如果是等待答复、
|
||
等待新请求这种依赖玩家操作的协程,其就绪时间就完全不可预测了。
|
||
正是这种不可预测的等待时间才使得我们调度器只能小睡个几毫秒。
|
||
|
||
如何多睡一段时间?
|
||
------------------
|
||
|
||
假设有多个房处于delay中,那么能睡的最长时间就是其中所有delay剩余时间的最小值。
|
||
但问题在于那些不可预测的就绪时间。如果另一个线程能告诉Lua答复已经就绪的话,
|
||
那么睡眠问题就好解决的多了。
|
||
|
||
实际上,可以借助信号量机制。当调度器开始睡觉时,肯定是调用了cpp函数。
|
||
这个cpp函数里面先将一个信号量置为0,然后再tryAcquire这个信号量一段时间。
|
||
当收到答复/收到请求时,把这个信号量+1。这样调度器就知道自己该醒过来啦。
|
||
|
||
如何避免房间等待太久?
|
||
----------------------
|
||
|
||
目前的调度思路如下:
|
||
|
||
- 若就绪队列为空,那么从所有运行中的房间进行一遍筛选。
|
||
- 若还为空,再筛选。
|
||
- 假如第二遍还空,那就睡觉。
|
||
- 假如就绪队列不空,那么弹出第一个,然后把控制权交给这个协程,等他自己让出。
|
||
|
||
在这个过程中,由于有些房间在等待就绪队列变空的过程中满足了就绪的条件,
|
||
那么再次筛选时,有可能重复的房间又会再次位于就绪队列靠前的位置。
|
||
这虽然不会导致某个房间永久等待下去的情况,但却可能让一个房间等待太长时间,
|
||
那我就又要被艾特了。
|
||
|
||
一种或许可行的解法是,在再次刷新队列的时候,优先选出上次队列里面没有的房间。
|
||
不知道这样如何呢?
|
||
|
||
怎么把现有的改成这样?
|
||
----------------------
|
||
|
||
CPP代码方面,首先Room已经不再是一个线程了,自然不能再当QThread的子类。
|
||
另外开个什么类来占据线程吧。
|
||
|
||
既然不是QThread的子类的话,那Room::run就得改了。原本是启动线程,现在还是改成
|
||
pushRequest吧。新请求立刻唤醒调度器,然后调度器切到请求处理协程处理新房间请求,
|
||
然后就开战!
|
||
|
||
既然要pushRequest,那么原先的请求队列也要从Room中移走了,这个得直接归属于线程。
|
||
当然,lua_State也要从Room撤走。
|
||
|
||
Lua代码方面,首先由于Engine成了所有房间共用的变量,必须杜绝所有对Engine
|
||
内变量的修改。点名cost_data和card.mark,这两个东西得通过__index大法托管给
|
||
Room挂着。剩下的没啥好说的,当然还有实现调度器。
|
||
|
||
协程池
|
||
------
|
||
|
||
虽然题文无关,但是FK确实在运行过程中不断的产生着协程。
|
||
为了避免因为不断产生新的协程导致的开销,可以使用协程池来管理。
|
||
|
||
用完的协程就存在某个数组留待下次使用。当需要启动一个新协程的时候,
|
||
先从协程池找,没有的话就新建一个。
|