Go语言并发探究:带缓冲通道的异常行为分析
Go带缓冲通道协程执行行为解析
代码示例
import ( "fmt" "sync" ) func taskScheduler(totalTasks int, taskQueue chan int, wg *sync.WaitGroup) { defer wg.Done() for i := 0; i < totalTasks; i++ { fmt.Println("Scheduler is adding task to queue: ", i) taskQueue <- i } close(taskQueue) } func taskWorker(taskQueue chan int, wg *sync.WaitGroup) { defer wg.Done() for value := range taskQueue { fmt.Println("Working on task: ", value) } } func main() { var wg sync.WaitGroup taskQueue := make(chan int, 5) wg.Add(2) go taskScheduler(10, taskQueue, &wg) go taskWorker(taskQueue, &wg) wg.Wait() }
实际控制台输出
Scheduler is adding task to queue: 0 Scheduler is adding task to queue: 1 Scheduler is adding task to queue: 2 Scheduler is adding task to queue: 3 Scheduler is adding task to queue: 4 Scheduler is adding task to queue: 5 Scheduler is adding task to queue: 6 Working on task: 0 Working on task: 1 Working on task: 2 Working on task: 3 Working on task: 4 Working on task: 5 Working on task: 6 Scheduler is adding task to queue: 7 Scheduler is adding task to queue: 8 Scheduler is adding task to queue: 9 Working on task: 7 Working on task: 8 Working on task: 9
疑问与解答
为什么调度器没有在任务4处停止?
你预期缓冲区满(5个任务)时调度器会阻塞,但实际输出里调度器似乎连续加到了任务6,核心原因是标准输出的缓冲机制导致打印顺序和实际执行顺序不一致:
- 代码中是先执行
fmt.Println再写入通道,调度器在写入任务4后,通道缓冲区已满,接着执行任务5的打印,随后尝试写入任务5时触发阻塞,此时Go runtime才会切换到Worker协程执行。 - 但
fmt.Println的输出会被缓冲,调度器的0-6的打印内容被暂时存在输出缓冲区中,直到缓冲区满或触发刷新(比如Worker协程的输出操作),才会一次性显示到控制台,造成“调度器没停止”的视觉错觉。
为什么两个协程出现批量轮流执行的情况?
这种“批量处理”的现象由两个因素共同导致:
- 输出缓冲放大了批量效果:Worker协程读取通道元素时的打印操作同样会被缓冲,当它连续读取多个元素时,打印内容会一次性输出,看起来像是批量处理。
- Go runtime的调度策略:当阻塞的协程(调度器)解除阻塞后,Go runtime不会强制立刻切换回该协程,而是允许当前正在执行的协程(Worker)继续执行一段时间,直到它完成当前的循环迭代或触发其他调度点(比如再次进行通道操作、系统调用)。这种调度策略会导致协程出现“连续执行一批任务”的情况,而非严格的“读一个写一个”交替。
为什么和其他示例的表现不同?
Go协程的调度行为本身是非确定性的,不同环境(如操作系统、Go版本、CPU核心数)或代码细节(比如是否有额外的系统调用、休眠操作)都会影响调度时机。其他示例中“读一个写一个”的表现只是调度器恰好频繁切换的情况,而你遇到的批量执行也是完全正常的Go runtime行为,没有违反通道的语义规则。
内容的提问来源于stack exchange,提问作者codingLim
相关产品推荐
相关产品推荐

