【原创】请避免GO语言中的携程空跑(CPU突然激增)
其实GO语言从1.6版本开始非常不错了,GC性能优化非常到位,并且各种并行设计比从新实现一套C++版本的确是方便不少。
语言包也很多,库也相对稳定,完全可以适用于生产环境。
本文主要是给刚刚入门新手注意一个携程空跑的问题,因为这种问题可能在C++中也遇到过,只是一些代码书写习惯导致。
首先来看一段代码:
func (c *WSConn) processHandler() { for { select { case message, ok := <-c.processMsg: // 处理数据包 if !ok { break } Call(message.MsgHead.Id, c, message.MsgContext, int(message.MsgHead.Msglen)) } } }
以上代码是用于处理一个WEBSOCKET的二进制消息后转换为指定处理信息的行为。
但是有没有同学发现有什么问题?但是这段代码的确有问题,因为当连接销毁后会导致processHandler这个携程空跑,CPU完全占满,当你有多个连接出现这种问题后整台服务器就会爆掉。
首先processMsg是一个channel,这里如果连接关闭了会同时关闭掉这个channel,首先我们知道select本身会等待channel,这样是不会消耗CPU的,就像C中的select函数一样,本身是不消耗的(使用不当的略过)。
但是当channel关闭后,整个携程本因直接销毁,但是代码中的break导致select无限循环跑,程序出现空跑现象,这里的break是相对于select而言的,所以看上去没毛病可跑起来毛病很大。
所以如果当出现空跑或GO语言某个携程CPU激增,可以去查看是不是哪个channel和select在无限循环。
所以正确的代码是:
func (c *WSConn) processHandler() { for { select { case message, ok := <-c.processMsg: // 处理数据包 if !ok { return // 这里必须强制结束携程 } Call(message.MsgHead.Id, c, message.MsgContext, int(message.MsgHead.Msglen)) } } }
我的排错方法是使用http的一种性能分析方式
下面是详细代码:
main.go
package main import ( "log" "runtime" "net/http" // http包引入 _ "net/http/pprof" // 性能分析包引入 ) func main() { // 设置并行运行 runtime.GOMAXPROCS(2) logger.SetLogName("testserver.log") go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }() // 此处建立http专用的性能分析端口 webSock := knlWebsocket.Create(":88", "null") webSock.Listen() }
以上代码仅供抛砖引玉,无法通过编译,注意注释内的代码。
看代码很简单,import 2个包:
import ( "net/http" // http包引入 _ "net/http/pprof" // 性能分析包引入 )
然后main函数中加入代码:
go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }() // 此处建立http专用的性能分析端口
我在这里加入了一个携程来做性能分析,是因为我本身有自己的主处理逻辑,所以必须使用携程。
完成以上代码添加后,直接编译启动程序,访问地址:http://localhost:6060/debug/pprof/,然后就可以进行愉快的性能分析了