Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

在Go语言中实现一个类似ltask的库 #92

Open
hanxi opened this issue Dec 16, 2023 · 4 comments
Open

在Go语言中实现一个类似ltask的库 #92

hanxi opened this issue Dec 16, 2023 · 4 comments

Comments

@hanxi
Copy link
Owner

hanxi commented Dec 16, 2023

ltask 是一个 Lua 的多任务库,具体介绍可以见 ltask :Lua 的多任务库 , 它的核心功能就是:

It implement an n:m scheduler , so that you can run M lua VMs on N OS threads.
它实现了一个 n:m 调度程序,因此您可以在 n 个操作系统线程上运行 m 个 lua 虚拟机。

ltask 和 skynet 很类似,达到的一个效果是我很喜欢的,service 里的业务代码同一时刻只会在一个线程里运行,就是写业务逻辑只需要考虑单线程,写代码的心智负担就会少很多。

由于我目前的游戏项目是 golang 写的,所以我想试试在 golang 中实现一个类似的库,以达到一段游戏业务逻辑的代码同一时刻只会在一个 goroutine 中运行。golang 中的 goroutine 本身的 G-M-P 模型 已经是在 n 个操作系统线程上运行 m 个 goroutine 了,所以在调度这一块不会自己实现了,而且 goroutine 之间的消息传递有 channel,连消息队列都不用自己实现,只需要好好利用这些基建。

我的构思是这样的,每个 service 有一个接收消息的 channel,service 之间的消息投递有 2 种方式:

  • 直接投递到目标服务
  • 投递到一个公共的 channel,由 scheduler 来派发消息到目标服务

直接投递到目标服务的方式是有锁的,需要先获取到目标服务的 channel,然后往里面写数据。
投递到公共的 channel 是无锁的,创建服务时把公共的 channel 引用到服务里,直接写数据即可,分配消息到服务因为只在 scheduler 里执行,所以根据 service id 获取到 service 的接收消息的 channel 也是不用加锁的。

上面无论哪种方式实现,对于使用者来说,效果是一样的。

消息投递的接口预计是有 3 个:

  • Send
  • Call
  • AsyncCall
    Send 是直接发消息给目标服务,不阻塞。Call 是发完消息后阻塞等结果。AsyncCall 是发完消息后不等结果,结果回来后发消息回来执行回调函数。

目前一个服务只有一个 goroutine ,所以 Call 会导致整个服务卡住。那能不能一个服务分配多个 goroutine,并且控制一个服务内同一时刻只允许一个 goroutine 在运行。可以引入 goroutine pool ,然后改造 Call 的流程,发完消息阻塞前把锁交出去,让其他空的 goroutine 能运行,消息回来后获取到锁后才继续运行。

有想过用 goroutine 模拟 Lua 的 coroutine ,总有些别扭,还是放弃这个方案了。

goroutine pool 的方案有 2 种选择,固定 goroutine 数量的比较简单,动态的比较复杂,可以先考虑实现一个固定数量的看看效果。goroutine 的数量关系到 call 挂起的数量,太大浪费内存,太少影响并发。比如:如果 1 个服务管理 100 个玩家,如果 goroutine 的数量设置为 10,那么同一时刻只能处理 10 个玩家的请求,其他的就得前面的 10 个玩家处理完。这个设想目前还没有实现,有空再搞。

scheduler 也可以实现为一个服务,这样方便用统一的方式操作 schedule。保留 1 到 1024 特殊的服务 id 给特殊服务使用,scheduler 服务的 id 可以设置为 1。

为了方便扩展服务,新增了一个插件服务使用 golang 的 plugin 来加载 so 文件中的服务,暂定插件服务的 id 为 2。

目前只支持一个进程内的服务之间进行消息通讯,如果要跨进程就需要其他 rpc 支持,计划是使用 gRPC 来搞,接入 NATS 模块。

工程地址: https://github.com/hanxi/gtask ,仅用于学习,没有过多的测试。

@cquwb
Copy link

cquwb commented Feb 20, 2024

"service 里的业务代码同一时刻只会在一个线程里运行",如果是这个目的,只起一个goroutine跑不就好了吗?为什么还需要用多个再来实现这个调度呢

@hanxi
Copy link
Owner Author

hanxi commented Feb 20, 2024

"service 里的业务代码同一时刻只会在一个线程里运行",如果是这个目的,只起一个goroutine跑不就好了吗?为什么还需要用多个再来实现这个调度呢

一个服务运行在多个 goroutine 上的话是用来实现 call 其他服务时可以挂起等待的同时能处理其他消息。目前的实现就是一个服务只有一个 goroutine ,在 call 没返回前是不能处理别的消息的。

@cquwb
Copy link

cquwb commented Feb 22, 2024

目前实现的功能的优势就是发送消息的时候不用加锁?期待下博主更新多goroutine来执行任务。

@hanxi
Copy link
Owner Author

hanxi commented Feb 22, 2024

是的,目前的实现不会有函数重入问题。改成多 goroutine 就会有函数重入问题。估计是没时间写多 goroutine 的版本了。。。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants