关于fmt.Println与goroutine打印顺序不一致的问题咨询
true会出现在输出中间而非末尾? 这个问题的核心在于goroutine的并发调度特性,以及不同goroutine之间打印操作的无同步执行,咱们一步步拆解:
1. 代码里的并发逻辑本质
你启动了和CPU核心数相同的工作goroutine,每个goroutine内部是按顺序执行的:先循环打印i*i,完成循环后往messages channel发送true。同时还有一个独立的goroutine在监听messages,收到消息就打印true。
但这里的关键是:多个工作goroutine之间是并行运行的,接收消息的goroutine也和它们并发执行。
举个实际场景:假设你有4个CPU核心,4个工作goroutine同时跑。其中goroutine B可能先完成了1000次循环,立刻发送true;而goroutine A这时候还在打印98xxx的平方值。接收消息的goroutine收到true后会马上打印,这时候goroutine A还在继续打印后续的平方——这两个打印操作(A的fmt.Println(i*i)和接收goroutine的fmt.Println(true))之间没有任何同步约束,Go调度器可以随时切换这两个goroutine的执行权,所以true就会插在A的打印输出中间。
2. 关于fmt.Println锁的误区
你可能会疑惑:fmt.Println不是有内部锁吗?为什么还会出现顺序混乱?
没错,fmt.Println的内部锁只是保证单个fmt.Println的输出不会被其他fmt.Println的输出拆分(比如不会出现半拉数字加半拉true的乱码),但它完全不保证不同fmt.Println调用之间的执行顺序。锁只解决了输出的原子性问题,没解决并发场景下的顺序问题。
3. 如何让true始终出现在最后?
如果想让所有平方值都打印完毕后,再打印所有true,只需要调整代码的执行顺序:先等所有工作goroutine完成打印,再处理messages的接收和打印。
修改后的代码如下:
package main import ( "fmt" "runtime" "sync" ) func main() { // 给channel加缓冲,避免goroutine阻塞 messages := make(chan bool, runtime.NumCPU()) var wg sync.WaitGroup var x = 1000 wg.Add(runtime.NumCPU()) for i := 0; i < runtime.NumCPU(); i++ { go func(x int) { defer wg.Done() var i = 0 for i < x { i += 1 fmt.Println(i * i) } messages <- true }(x) } // 先等待所有工作goroutine完成所有打印操作 wg.Wait() // 关闭channel,告诉接收方没有新消息了 close(messages) // 现在再接收并打印所有true for i := range messages { fmt.Println(i) } }
这样调整后,所有工作goroutine的打印任务全部完成后,才会开始打印true,顺序就完全符合你的预期了。
内容的提问来源于stack exchange,提问作者Jal

