为何使用无缓冲channel时main() goroutine发送最后一个值后未阻塞?
Go无缓冲Channel场景下main Goroutine提前退出导致输出缺失的问题
你编写的Go代码如下:
package main import ( "fmt" ) func squares(c chan int) { for i := 0; i < 4; i++ { num := <-c fmt.Println(num * num) } } func main() { fmt.Println("main() started") c := make(chan int) go squares(c) c <- 1 c <- 2 c <- 3 c <- 3 fmt.Println("main() stopped") }
实际输出:
main() started 1 4 9 main() stopped
预期输出是1、4、9、9,并且存在疑问:使用无缓冲Channel时,main goroutine为何在发送最后一个值后未阻塞?添加time.Sleep后程序符合预期。
问题原因
- 无缓冲Channel的发送逻辑:无缓冲Channel的发送操作确实会阻塞,直到接收方完成接收。你遇到的情况不是发送未阻塞,而是发送完成后,main goroutine没有等待
squaresgoroutine完成后续的打印操作就直接退出了。 - main goroutine的特殊性:main goroutine是Go程序的入口,一旦它执行完所有语句,整个程序会立即终止,所有其他goroutine都会被强制终止,不管它们是否完成剩余任务。在你的代码中,main发送第四个值后,
squares已经接收到了这个值,但还没来得及打印9,main就执行了fmt.Println("main() stopped")并退出,导致最后一次输出被截断。 - time.Sleep的作用:添加
time.Sleep只是给了squaresgoroutine足够的时间完成最后一次打印,属于临时的 workaround,不是可靠的同步方式。
解决方案
方案1:使用额外的Channel同步
通过一个额外的done Channel,让main等待squares完成所有任务后再退出:
package main import ( "fmt" ) func squares(c chan int, done chan bool) { for i := 0; i < 4; i++ { num := <-c fmt.Println(num * num) } done <- true // 发送任务完成信号 } func main() { fmt.Println("main() started") c := make(chan int) done := make(chan bool) go squares(c, done) c <- 1 c <- 2 c <- 3 c <- 3 <-done // 阻塞等待squares完成 fmt.Println("main() stopped") }
方案2:使用sync.WaitGroup
适合管理多个goroutine的场景,通过WaitGroup等待所有任务完成:
package main import ( "fmt" "sync" ) func squares(c chan int, wg *sync.WaitGroup) { defer wg.Done() // 任务结束时标记完成 for i := 0; i < 4; i++ { num := <-c fmt.Println(num * num) } } func main() { fmt.Println("main() started") c := make(chan int) var wg sync.WaitGroup wg.Add(1) // 注册一个需要等待的goroutine go squares(c, &wg) c <- 1 c <- 2 c <- 3 c <- 3 wg.Wait() // 等待所有注册的任务完成 fmt.Println("main() stopped") }
总结
无缓冲Channel的发送操作本身是阻塞的,你遇到的输出缺失问题本质是main goroutine提前退出导致的。使用Channel或WaitGroup进行显式同步,才是可靠的解决方式,避免依赖time.Sleep这类不可靠的手段。
内容的提问来源于stack exchange,提问作者Orazbayeva Dana
相关产品推荐
相关产品推荐

