已取消的Context如何保证终止Goroutine?Select选择逻辑疑问
Go Select 多就绪Case的处理与终止保证
问题1:如何保证select不会持续选中<-intChan的case,确保<-ctx.Done()最终被选中?
Go的select在多个case同时就绪时,会从就绪的case中伪随机选择一个执行。从runtime实现和概率逻辑来看,不会出现永远忽略某个就绪case的情况——无限循环下,ctx.Done()的case最终一定会被选中。如果需要更主动的确定性保证,可以通过以下方式优化:
方式1:循环前置ctx.Done()检查
在每次select之前,先非阻塞地检查ctx是否已取消,一旦取消直接返回:
func Consumer(ctx context.Context, intChan chan int) { for { // 先非阻塞检查ctx是否取消 select { case <-ctx.Done(): return default: } // 再处理通道接收或ctx取消 select { case <-ctx.Done(): return case i := <-intChan: foo(i) } } }
这样只要ctx已取消,下一次循环就会直接触发退出,无需等待select的随机选择。
方式2:依赖runtime的公平性
Go runtime的select实现不会出现无限次跳过某个就绪case的情况,即使是随机选择,长期来看每个就绪case都有被选中的机会。因此即使不做额外处理,无限循环下ctx.Done()最终也会被选中,只是无法确定具体是哪一次循环。
问题2:测试代码中Consumer和Producer总能终止,为何不会出现永远不执行<-ctx.Done()的情况?
这是因为Go的select在处理多个就绪case时,采用公平的伪随机选择策略:runtime内部会维护就绪case的列表,每次选择时不会固定偏向某个case,不会出现无限次选中同一个case的情况。
在测试代码中:
- 当
cancelFunc()被调用后,ctx.Done()通道变为就绪状态,此时Consumer和Producer中的select都有两个就绪case; - 每次select执行时,两个就绪case都有被选中的概率,经过有限次循环后,必然会选中
<-ctx.Done()的case,从而退出循环; - Producer中的发送goroutine同样会在选中
<-ctx.Done()后退出,最终两个goroutine都会终止,wg.Wait()会完成。
从概率角度来说,无限次循环中永远不选中某个就绪case的概率为0,所以测试代码总能正常终止。
内容的提问来源于stack exchange,提问作者fishfishin
相关产品推荐
相关产品推荐

