如何保证golang的协程可控-协程管理器

设计目的
研发过程和实际使用中发现golang中开启协程很容易,但是管理起来很麻烦,无法有效检测协程内容运行情况,协程崩溃,协程使用的chan阻塞情况,并且无限制增长的协程会为程序带来风险
经过之前讨论golang的协程需要管理的要点,总结为以下几点:
1. 每个协程需要有唯一的命名
2. 定时输出每个协程使用的chan的使用情况,并且确认数据是在流动的
3. 除特殊协程外(玩家链接协程属于特殊协程),协程必须要有合适的崩溃捕捉,在其崩溃后能够迅速恢复
4. 定时对协程的运行情况进行采集,对于有风险的协程及时发出告警
5. 能够通过日志有效追溯协程出现问题的原因
 
设计思路
为协程抽象一个接口和一个统一管理器,所有协程都需要实现统一接口和委托管理器来进行管理。达到统一实现以上设计目的
管理器设计:
1. 管理器需要在协程被加入管理器时启动协程
2. 管理器需要在协程被移除管理器时停止协程
3. 管理器需要为协程进行唯一编号,方便协程打印状态时区分
4. 管理器需要保证停止时统一安全停止所有协程
5. 管理器需要保证协程异常停止时打印堆栈信息
6. 管理器需要保证协程异常停止时,有重新拉起协程的能力
 
协程接口设计:
1. 需要实现协程运行的主逻辑
2. 需要有打印运行状态的接口
3. 需要在协程退出时进行析构
设计说明
管理器添加协程方法,协程添加进管理器后启动协程
func (manager *ThreadManager) StartThread(thread Thread)
 
管理器移除协程方法,协程从管理器中移除协程时需要同时停止协程
func (manager *ThreadManager) StartThread(thread Thread)
 
管理器提供了统一创建协程基类的方法,为管理的协程统一进行编号
func (manager *ThreadManager) NewBaseThread(namePrefix string, keepAlive, sampling bool) *BaseThread
namePrefix 参数:自定义协程前缀,其中必须保留3个%d_%d_%d,用于填充唯一命名中的ServerID(区服ID或者服务器ID)、ModuleID(没有可填写0)和管理器生成的唯一编号
 
管理器需要保证停止时统一安全停止所有协程,调用此方法会正确停止所有加入管理器的协程并且触发协程关闭的钩子,以供协程接口实现停止后的功能处理
func (manager *ThreadManager) Stop()
 
管理器设计的5、6需求在管理器调用的其他方法实现
func StartThread(t Thread)
这个方法里面负责控制协程的生命周期
1. 捕捉协程出现的运行时异常,并且打印堆栈信息
2. 保持正常运行时协程不退出
3. 对需要保活的协程出现不可控异常时进行保活(如时间轮协程)
4. 对不需要保活的协程出现不可控异常时触发正常关闭的钩子,保证退出流程正常(如玩家的链接协程)
5. 对正常退出的协程触发正常关闭的钩子,保证退出流程正常
 
接口主要方法设计
GetThreadName() string //取得协程名称
主要用于打印日志时使用
NeedKeepAlive() bool //当出现崩溃后是否需要重新拉起(是否保活)
异常发生时是否需要重新拉起
NeedSampling() bool    //是否需要执行采样
是否需要对协程状态进行采样
IsStopped() bool       //是否主动停止的
用于判定是否是正常的停止,用于流程控制
 
以下部分需要实例自己实现
 
SamplingPrint()        //采样输出 需要实例自己实现
Stop(bool)             //主动停止时调用(需要自己定义协程停止的操作,主要是协程接收什么样的信号才退出)
Run() bool             //协程实现的功能 需要实例自己实现返回false就会执行退出协程
AfterCycleBreak()      //Thread主循环被打破后的处理 需要实例自己实现(关闭chan等析构操作)
 
 
通过以上接口和管理器的设计,就可以把控协程的生命周期,完成协程可控
具体的生命周期逻辑在func StartThread(t Thread)中,请结合代码理解

Golang协程管理模块使用说明

管理器开启时机
开启方法 func (manager *ThreadManager) Start(serverID int, moduleID int)
管理器的生命周期基本与应用的生命周期相当,在加载必要的应用信息后,应当尽早的启动管理器,至少要保证管理器的启动要在向其添加协程管理之前
 
 
以下公用代码会向管理器中添加协程,请保证使用以下代码时,管理器已经被启动
tcpframework.GetTimeWheelManager().Init(true) //启动时间轮
tcpframework.StartClient(cent.gameMasterClient) //启动TcpClient
logic.innerServer.Run() //TCPServer的启动
task.Start() //TcpTask的启动方法这个方法一般在业务代码中不会使用,只在TCPServer收到链接后才会使用
 
管理器关闭时机
关闭方法 func (manager *ThreadManager) Stop()
管理器应该尽可能晚的被关闭,因为这会停止掉所有正在管理的协程,如果协程需要有序退出,请先调用
func StopThread(t Thread, manual bool)先退出需要有序退出的协程
 
 
Tips:
1. 心跳管理已经从时间轮中移除了,在TcpTask和TcpClient中自己维护,时间轮不需要再关心链接心跳的问题
2. 原协程启动方式不在需要在前面+go
如:tcpframework.RunTCPClient 和chat.innerServer.Run()前面的go需要去除
 
 
特殊要求
所有NewClient的方法中需要为3个属性初始化
    c.WriteThread = tcpframework.NewTcpClientWriteThread©
    c.ReadThread = tcpframework.NewTcpClientReadThread©
    c.RunThread = tcpframework.NewTcpClientRunThread(time.Duration(heartBeatTime), c)
 
由于目前设计上的一些缺陷,目前使用时,client启动前一定要为其读写和运行协程赋值

你可能感兴趣的:(golang,开发语言,后端,游戏,服务器)