
当使用for-range遍历go中的无缓冲或带缓冲但未关闭的通道时,循环会持续等待新数据,直到通道被显式关闭;若未关闭,程序将永久阻塞,无法执行后续逻辑。
当使用for-range遍历go中的无缓冲或带缓冲但未关闭的通道时,循环会持续等待新数据,直到通道被显式关闭;若未关闭,程序将永久阻塞,无法执行后续逻辑。
在Go并发编程中,`for range ch` 是读取通道的常见写法,但它隐含一个关键前提:**该通道最终必须被关闭**,否则循环永远不会退出。你的程序中创建了一个带缓冲的通道 `ch := make(chan *HttpResponse, len(urls))`,并启动了三个 goroutine 向其中发送结果,但从未调用 `close(ch)`。因此,`for i := range ch` 会在接收完全部3个值后继续阻塞,等待第4个(不存在的)值,导致 `"Im done"` 永远不会打印。正确的做法是:明确知道要接收的数据总量时,优先使用计数循环而非 range。修改 main 函数如下:
func main() {
fmt.Println("start")
ch := make(chan *HttpResponse, len(urls))
for _, url := range urls {
go asyncHttpGets(url, ch)
}
// 显式接收 len(urls) 个结果,避免阻塞
for i := 0; i < len(urls); i++ {
result := <-ch
fmt.Println(result)
}
fmt.Println("Im done")
}⚠️ 注意事项:
- for range ch 仅适用于生产者明确关闭通道的场景(例如:所有 goroutine 完成后由主协程或某一方调用 close(ch));
- 若使用 range,务必确保有且仅有一个 goroutine 负责关闭通道(通常在所有发送完成后),否则可能引发 panic(重复关闭)或死锁;
- 带缓冲通道(如本例 make(chan T, 3))虽能暂存数据,但不改变 range 的语义——它仍等待“通道关闭信号”,而非“缓冲区为空”。
补充建议:为提升健壮性,可为 HTTP 请求添加超时控制,并在 asyncHttpGets 中处理 err != nil 情况(如 Google 模拟超时失败时,resp 为 nil,应避免解引用)。但核心阻塞问题的根本解法,始终是避免对未关闭通道使用 for range。


















