golang开发开房间游戏的一种无锁设计

游戏开发 18-06-10 21:52 2413  

背景 游戏开发中,经常会遇到“开房间”的游戏逻辑,比如组队下一个副本,开房间来一场赛场,开房间玩一局跳舞游戏,建房间进行一局斗地主/麻将游戏等等。而一局基于房间的游戏中,可能会涉及客户端消息,rpc响应,定时器回调,技能CD倒计时触发,发射物到期判定,超时自动行动,超时掉线自动处理玩家信息等待情况, 大部分情况下,在同一个“房间”里面的各种消息都可以串行处理。 普通的房间逻辑设计 因为在同一个房间里面有各种消息和事件的触发,会涉及到多个并发的逻辑执行流。在没有深度考虑和设计的情况下,自然而然的处理方式是,各个并发逻辑同时访问房间的共享基础数据,通过加锁对基础数据进行保护。 比如一局棋牌游戏中,房间游戏逻辑正在进行,新进入的玩家需要获取房间里面所有位置的玩家信息;当前行动玩家的行动逻辑会访问房间数据;定时器超时触发完成玩家的超时默认动作;另外的定时器会检查有没有超时掉线玩家需要踢出等等。他们的逻辑都是一个独立的执行流,特别是在golang开发中,启用一个独立的routine去处理每个逻辑流是很容易的事情。导致对所有房间里面共享信息的访问都需要加锁控制。 无锁房间逻辑设计 鉴于游戏过程中,房间相关的逻辑会有多种多样的消息和事件发生,但是同一个房间里面的消息基本是可以串行执行的,而并不影响他们的逻辑。因此,为了做到无锁,需要将各种杂七杂八的执行逻辑流通过某种方式串行化起来,保证房间数据的访问都是串行唯一的,自然就不需要锁了。 在golang中,语言自带的channel就是非常适合这种场景。为每个房间简历一个buffer channel,所有方将相关的消息和事件统一发送到channel中,在channel的读端依次读取每个消息处理。 需要注意的是,channel本身是在语言层面提供了锁机制,不同的并发逻辑流在写channel时不需再自行加锁;虽然channel本身也加锁,但是语言层肯定比自己额外添加锁机制更高效。 另外,为能够通过channel统一发送各种信息(客户端新消息,rpc响应,各种定时器事件,后台控制命令等等),需要将各种数据统一定义,以便在同一个channel中处理,比如类似下面: type RoomEvent struct{ RoomID uint32 //房间ID EventType uint32 //事件类型 EventData interface{} //事件数据 } 通过以上封装可以将各种不同的数据规整后发送到channel中,channel读取端再更具不同的EventType解码出具体的时间数据做处理。 无锁设计的意义 在程序优化到后期,锁的消耗会逐渐成为新的性能消耗大头,在结构上做一些无锁设计可以进一步提高性能。

yuanxb 2019-07-04 13:56

果然搜到了一样想法的人