Go语言带缓冲通道未阻塞疑问:为何输出顺序不符合预期?
解答你的Go通道与goroutine调度疑问
先直接拆解你的疑问:这段代码里的缓冲通道并没有发生阻塞的原因,是因为两次发送操作都恰好发生在通道有空位的时候;而In f()出现在2之前,是goroutine调度和标准输出IO的特性共同导致的。
先理清楚核心规则
你的通道c是容量为1的缓冲通道,核心规则很明确:
- 当通道内元素数量小于容量时,发送操作
c <- x不会阻塞,直接把元素放入通道; - 当通道满了(元素数量等于容量)时,发送操作会阻塞,直到有元素被取出、通道腾出空位。
符合你输出的实际执行流程
我们一步步拆解最可能的执行顺序:
- 主goroutine(
main)启动,创建容量为1的通道c,随后启动子goroutinef()。此时f()不会立刻执行,而是进入Go调度器的等待队列。 - 主goroutine继续执行
c <- 2:此时通道是空的,直接把2放入,通道进入满状态。 - 主goroutine执行
fmt.Println(<-c):取出通道里的2,通道变回空状态。这时候主goroutine需要执行标准输出操作,这个IO操作可能触发调度器切换到等待的子goroutinef()。 - 子goroutine
f()开始执行:- 执行
c <- 1:通道是空的,直接放入1,全程无阻塞; - 执行
fmt.Println("In f()"):向标准输出写入一行内容,因为行缓冲特性,这行内容会立刻被刷新到控制台。
- 执行
- 调度器切回主goroutine:
- 之前的
fmt.Println(<-c)操作完成,把2输出到控制台; - 执行第二个
fmt.Println(<-c):取出通道里的1并输出。
- 之前的
为什么你会觉得“应该阻塞”?
你可能默认子goroutinef()会和主goroutine的发送操作同时执行,但Go的调度器是抢占式的,它会根据当前goroutine的执行状态(比如遇到IO、系统调用或时间片耗尽)切换执行权。在这个场景里,子goroutine的发送操作是在主goroutine取出第一个元素、通道变空之后才执行的,完全符合缓冲通道的规则,自然不会阻塞。
为什么In f()出现在2之前?
这是标准输出的行缓冲特性和goroutine调度共同作用的结果:
- 主goroutine的第一个
fmt.Println(<-c)虽然先执行了代码逻辑,但IO操作的完成可能比子goroutine的fmt.Println("In f()")慢; - 子goroutine的输出先被刷新到控制台,所以你会看到
In f()先显示出来,之后才是主goroutine输出的2。
验证小技巧
如果在每个fmt.Println前加上当前goroutine的标识,就能更清晰看到执行逻辑顺序:
package main import ( "fmt" "runtime" ) var c = make(chan int, 1) func f() { c <- 1 fmt.Printf("子goroutine: In f()\n") } func main() { go f() c <- 2 fmt.Printf("主goroutine: %d\n", <-c) fmt.Printf("主goroutine: %d\n", <-c) }
你会发现代码的执行逻辑是主goroutine先取出2,然后子goroutine执行打印,最后主goroutine取出1,只是控制台的输出顺序因为IO调度看起来颠倒了。
内容的提问来源于stack exchange,提问作者Ivan
相关产品推荐
相关产品推荐

