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
技术问题
- 执行
squareChan <- testNum后,调度器为何优先调度cube协程而非square协程? - 执行
cubeChan <- testNum后,主协程为何未发生阻塞?
协程调度切换全流程解析
结合输出顺序,拆解三个协程的调度逻辑:
- 初始启动:主协程启动后打印第1行,创建两个无缓冲通道,随后启动
square和cube子协程,接着打印第2行。 - 主协程第一次阻塞:主协程执行
squareChan <- testNum,因无缓冲通道无接收方,主协程进入阻塞状态。 - 调度cube协程:主协程阻塞后,调度器从就绪队列中选取
cube协程执行,打印第3行;cube协程执行num := <-cubeChan,因通道无数据,进入阻塞。 - 调度square协程:
cube协程阻塞后,调度器切换到square协程,打印第4行;square协程接收squareChan的数据,主协程被唤醒;square协程打印第5行,随后向通道发送计算结果,因主协程未准备接收,square协程阻塞。 - 主协程恢复执行:主协程唤醒后打印第6、7行,执行
cubeChan <- testNum。 - 主协程无阻塞发送:此时
cube协程已阻塞在接收操作,主协程发送数据时直接完成传递,cube协程被唤醒,主协程无需阻塞,继续打印第8、9行。 - 主协程等待接收:主协程执行接收两个通道数据的操作,进入等待状态。
- 子协程收尾:调度器先切换到
square协程,完成发送后打印第10行并退出;再切换到cube协程,打印第11行后发送计算结果,完成传递后打印第12行并退出。 - 主协程收尾:主协程接收到数据后计算总和,打印第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
相关产品推荐
相关产品推荐

