自定义简易协程池踩坑记录

前言

上一篇文章中完善了一个自定义协程池,正所谓不改就没事,哈哈,果然上周测试环境发版后就出问题了,本人的服务的健康检查一发新版本之后就会立马unhealthy,然后先是观察容器cpu负载竟然是100%,好家伙,登陆容器通过top命令确定了就是自己的服务导致的,然后检查了自己修改过的代码,经过半天的排查果然找到了这个病因。

代码展示(问题代码部分)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//运行一个groutine 开始消费任务 核心的问题就出在了下面这个方法内 
//for循环中嵌套了select 然后通过关闭通道进行跳出
//然而我之前的跳出用的是break,这里就是关键了
//break只能是跳出当前select,外层的for循环还是继续走的,然后就进入了死循环了
func (self *Pool) runGroutine() { // runningWorkers + 1
self.incRunningWorkers() //worker运行ing数量原子自增1
go func() {
defer func() {
self.decRunningWorkers() //worker运行ing数量原子自减1
if r := recover(); r != nil {
// if self.PanicHandler != nil {
// self.PanicHandler(r)
// } else {
// log.Printf("Worker panic: %s\n", r)
// }
err := errs.New(Panic_Sub_Goroutine, "子协程panic") //子协程panic是会导致主协程挂掉的,这一步也是必须进行捕获处理
self.ResultQuene <- err
}
self.checkWorker() // 兜底机制,避免worker全部panic后没有worker消费队列中的数据,理论上这一步非常重要!否则有可能出现死锁状态
}()
for {
select {
case task, ok := <-self.TaskQueue:
if !ok {
//break
return //这里不要用break break只能跳出一层第二层的for循环还会继续 会直接把cpu打满
}
err := task()
self.ResultQuene <- err
}
}
}()
}

总结

好在是在测试环境发布时发现了这样的问题,如果是线上出现该问题还是比较麻烦的,总结就是对select的一些用法还是不够熟悉,下一篇文章就来对golang中的select用法进行一下总结(点此处了解select用法总结)