Go并发调度协程对接worker pool时栈空越界问题及优化咨询
问题根因
你遇到的下标越界问题是Go的select语句的特性导致的:select会在进入语句块时一次性评估所有case的收发操作表达式,不管最终哪个case被选中。只要stack为空,jobchan <- stack[0]这个表达式里的stack[0]就会被执行,直接触发越界panic。
修复方案
你可以通过动态控制jobchan是否出现在select的case中来规避这个问题:当stack长度为0时,将用于发送的通道变量设为nil,nil通道的发送操作会永久阻塞,select会直接忽略这个case。
修改后的代码如下:
func dispatch() { var stack []string acount := 0 for { // 动态声明发送用的通道和待发送值 var sendChan chan<- string var sendVal string if len(stack) > 0 { sendChan = jobchan sendVal = stack[0] } select { case d := <-dispatchchan: stack = append(stack, d) case c := <-mw: acount += c case sendChan <- sendVal: // 只有当stack不为空时这个case才会被选中,直接操作栈即可 if len(stack) > 1 { stack[0] = stack[len(stack)-1] stack = stack[:len(stack)-1] } else { stack = nil } default: if acount == 0 && len(stack) == 0 { close(jobchan) close(dispatchchan) close(mw) wg.Done() return } } } }
符合Go惯用法的优化建议
- 你当前用
mw做worker计数信号量的设计是可行的,也可以直接用sync.WaitGroup跟踪正在运行的任务,代码可读性会更高 - 如果你的任务处理不需要严格的栈式后进先出调度,建议直接用带缓冲的通道做待办队列,不需要自己维护切片,代码会更简洁,也能避免并发操作切片的潜在问题
- 生产环境可以直接用成熟的worker池开源库实现类似功能,不需要自己手写调度逻辑,减少出错概率
内容的提问来源于stack exchange,提问作者Jim
相关产品推荐
相关产品推荐

