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

Go无缓冲通道写入后主协程未阻塞?调度切换逻辑解析

Go协程通道调度问题解析

示例代码

package main

import "fmt"

func square(c chan int) {
    fmt.Println("[square] reading")
    num := <-c
    fmt.Println("[square] before writing")
    c <- num * num
    fmt.Println("[square] after writing")
}

func cube(c chan int) {
    fmt.Println("[cube] reading")
    num := <-c
    fmt.Println("[cube] before writing")
    c <- num * num * num
    fmt.Println("[cube] after writing")
}

func main() {
    fmt.Println("[main] main() started")

    squareChan := make(chan int)
    cubeChan := make(chan int)

    go square(squareChan)
    go cube(cubeChan)

    testNum := 3
    fmt.Println("[main] sent testNum to squareChan")

    squareChan <- testNum

    fmt.Println("[main] resuming")
    fmt.Println("[main] sent testNum to cubeChan")

    cubeChan <- testNum // Why main not blocked here? 
 
    fmt.Println("[main] resuming")
    fmt.Println("[main] reading from channels")

    squareVal, cubeVal := <-squareChan, <-cubeChan
    sum := squareVal + cubeVal

    fmt.Println("[main] sum of square and cube of", testNum, " is", sum)
    fmt.Println("[main] main() stopped")
}

运行输出

1.[main] main() started
2. [main] sent testNum to squareChan
3. [cube] reading
4. [square] reading
5. [square] before writing
6. [main] resuming
7. [main] sent testNum to cubeChan
8. [main] resuming
9. [main] reading from channels
10. [square] after writing
11. [cube] before writing
12. [cube] after writing
13. [main] sum of square and cube of 3  is 36
14. [main] main() stopped

技术问题

  1. 执行squareChan <- testNum后,调度器为何优先调度cube协程而非square协程?
  2. 执行cubeChan <- testNum后,主协程为何未发生阻塞?

协程调度切换全流程解析

结合输出顺序,拆解三个协程的调度逻辑:

  1. 初始启动:主协程启动后打印第1行,创建两个无缓冲通道,随后启动square和cube子协程,接着打印第2行。
  2. 主协程第一次阻塞:主协程执行squareChan <- testNum,因无缓冲通道无接收方,主协程进入阻塞状态。
  3. 调度cube协程:主协程阻塞后,调度器从就绪队列中选取cube协程执行,打印第3行;cube协程执行num := <-cubeChan,因通道无数据,进入阻塞。
  4. 调度square协程:cube协程阻塞后,调度器切换到square协程,打印第4行;square协程接收squareChan的数据,主协程被唤醒;square协程打印第5行,随后向通道发送计算结果,因主协程未准备接收,square协程阻塞。
  5. 主协程恢复执行:主协程唤醒后打印第6、7行,执行cubeChan <- testNum。
  6. 主协程无阻塞发送:此时cube协程已阻塞在接收操作,主协程发送数据时直接完成传递,cube协程被唤醒,主协程无需阻塞,继续打印第8、9行。
  7. 主协程等待接收:主协程执行接收两个通道数据的操作,进入等待状态。
  8. 子协程收尾:调度器先切换到square协程,完成发送后打印第10行并退出;再切换到cube协程,打印第11行后发送计算结果,完成传递后打印第12行并退出。
  9. 主协程收尾:主协程接收到数据后计算总和,打印第13、14行,程序结束。

问题解答

问题1:执行squareChan <- testNum后,调度器为何优先调度cube协程而非square协程?

Go的M:N调度器在选取就绪协程时,没有严格的启动顺序约束。square和cube协程启动后都会进入调度队列,调度器的选取是非确定性的——这是调度器为了平衡负载设计的策略,除非通过通道、互斥锁等同步手段强制指定顺序,否则无法保证哪个协程先被执行。本次案例中cube协程先被选中只是随机结果。

问题2:执行cubeChan <- testNum后,主协程为何未发生阻塞?

无缓冲通道的发送操作是否阻塞,核心取决于是否有对应接收协程已处于等待状态:

  • 当主协程执行cubeChan <- testNum时,cube协程已经阻塞在num := <-cubeChan的接收操作上(早在第3步就进入阻塞)。
  • 此时发送方(主协程)和接收方(cube协程)直接完成数据传递,无需等待,因此主协程不会阻塞,直接继续执行后续代码。

内容的提问来源于stack exchange,提问作者Alex

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 16:17:16