FreeKill/docs/dev/schedule.rst
notify b75d8afe62
Scheduler (#194)
简单协程调度器的实现,详细说明请看 docs/dev/scheduler.rst
2023-06-16 10:56:33 +08:00

103 lines
4.8 KiB
ReStructuredText
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

.. 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确实在运行过程中不断的产生着协程。
为了避免因为不断产生新的协程导致的开销,可以使用协程池来管理。
用完的协程就存在某个数组留待下次使用。当需要启动一个新协程的时候,
先从协程池找,没有的话就新建一个。