You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go语言带缓冲通道未阻塞疑问:为何输出顺序不符合预期?

解答你的Go通道与goroutine调度疑问

先直接拆解你的疑问:这段代码里的缓冲通道并没有发生阻塞的原因,是因为两次发送操作都恰好发生在通道有空位的时候;而In f()出现在2之前,是goroutine调度和标准输出IO的特性共同导致的。

先理清楚核心规则

你的通道c是容量为1的缓冲通道,核心规则很明确:

  • 当通道内元素数量小于容量时,发送操作c <- x不会阻塞,直接把元素放入通道;
  • 当通道满了(元素数量等于容量)时,发送操作会阻塞,直到有元素被取出、通道腾出空位。

符合你输出的实际执行流程

我们一步步拆解最可能的执行顺序:

  1. 主goroutine(main)启动,创建容量为1的通道c,随后启动子goroutinef()。此时f()不会立刻执行,而是进入Go调度器的等待队列。
  2. 主goroutine继续执行c <- 2:此时通道是空的,直接把2放入,通道进入满状态。
  3. 主goroutine执行fmt.Println(<-c):取出通道里的2,通道变回空状态。这时候主goroutine需要执行标准输出操作,这个IO操作可能触发调度器切换到等待的子goroutinef()。
  4. 子goroutinef()开始执行:
    • 执行c <- 1:通道是空的,直接放入1,全程无阻塞;
    • 执行fmt.Println("In f()"):向标准输出写入一行内容,因为行缓冲特性,这行内容会立刻被刷新到控制台。
  5. 调度器切回主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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 12:42:37