多Goroutine同步退出场景中无缓冲exit通道末尾接收语句的执行逻辑疑问
解释这段Go代码的Goroutine退出逻辑
首先要明确:这段代码的实际行为并不会等待所有20个Goroutine都退出后才执行主goroutine的<-exit语句——这可能是你测试时的调度巧合或者误解了输出。下面我们拆解代码的核心逻辑,帮你理清其中的关键:
1. 无缓冲通道的核心特性
Go的无缓冲通道(chan bool)是同步阻塞的:
- 当执行
ch <- val时,发送方会一直阻塞,直到有某个接收方执行<-ch完成配对。 - 当执行
<-ch时,接收方也会一直阻塞,直到有某个发送方执行ch <- val完成配对。
这个特性是理解整个流程的基础。
2. 主Goroutine的执行流程
主goroutine的关键步骤时序:
- 启动20个worker goroutine,每个都会进入
select循环,等待exit信号或inCh的数据。 time.Sleep(1*time.Second)确保所有worker都启动并进入等待状态。- 执行
exit <- true:此时主goroutine会阻塞,直到任意一个worker goroutine接收这个信号。 - 当有worker接收完信号后,主goroutine解除阻塞,立即执行
<-exit:再次阻塞,等待有人向exit发送数据。
3. Worker Goroutine的执行流程
被选中接收exit信号的worker(比如G1)会执行以下步骤:
- 打印
Exit received from X。 - 执行
exit <- true:此时这个worker会阻塞,直到有接收方匹配这个发送操作。 - 当发送完成后,执行
return退出goroutine。
4. 实际的执行时序
当主goroutine的exit <- true被G1接收后:
- 主goroutine立刻走到
<-exit语句,开始等待接收。 - G1的
exit <- true会和主goroutine的<-exit直接配对:G1完成发送后退出,主goroutine完成接收后打印Final exit,程序直接终止。 - 剩下的19个worker goroutine此时还在
select循环中等待,会被程序终止强制退出,并不会执行退出逻辑。
为什么你可能误以为所有worker都退出了?
如果你的测试输出中出现了多个worker的退出信息,大概率是Go调度器的随机行为导致的巧合:比如主goroutine在完成exit <- true后,调度器没有立刻切换到主goroutine执行<-exit,而是先调度了其他worker接收G1发送的exit <- true。但这种情况是不可靠的,完全依赖调度器的行为,不能保证所有worker都能收到退出信号。
正确的多Goroutine同步退出方案
如果想要真正等待所有goroutine退出,推荐使用sync.WaitGroup结合退出通道的方案,或者使用context.Context(更优雅的方式):
使用context.Context的示例:
package main import ( "context" "fmt" "sync" "time" ) func main() { ctx, cancel := context.WithCancel(context.Background()) var wg sync.WaitGroup for i := 0; i < 20; i++ { wg.Add(1) go func(instance int) { defer wg.Done() fmt.Println("In go routine ", instance) for { select { case <-ctx.Done(): fmt.Println("Exit received from ", instance) return // case value := <-inCh: // fmt.Println("Value=", value) } } }(i) } time.Sleep(1 * time.Second) cancel() // 发送退出信号给所有goroutine wg.Wait() // 等待所有goroutine完成退出 fmt.Println("Final exit") }
这个方案中:
context.WithCancel创建的上下文可以同时通知所有监听ctx.Done()的goroutine。sync.WaitGroup确保主goroutine等待所有worker完成退出后再继续执行。
内容的提问来源于stack exchange,提问作者Makis Papapanagiotou
相关产品推荐
相关产品推荐

