
本文详解 go 语言中使用 channel 传递字符串时,因混淆字符串遍历机制导致“逐字符输出”的典型错误,并提供基于 sync.waitgroup 的健壮解决方案。
本文详解 go 语言中使用 channel 传递字符串时,因混淆字符串遍历机制导致“逐字符输出”的典型错误,并提供基于 sync.waitgroup 的健壮解决方案。
在 Go 中,string 是不可变的字节序列,但 for range 遍历字符串时按 rune(Unicode 码点)而非字节迭代——这本身并无问题;真正引发困惑的是:开发者常误将从 channel 接收的单个 string 值当作可迭代的“字符集合”来处理,从而写出类似 for _, r := range results { fmt.Println(string(r)) } 的代码。实际上,results :=
正确接收多个 channel 值:使用 for range c
要接收所有 goroutine 发送的字符串,应直接遍历 channel:
// ✅ 正确:遍历 channel 中的所有 string 值
for s := range c {
fmt.Println(s) // 输出 5 次 "helloooo"
}
但此写法要求 channel 必须被关闭,否则 for range 永不结束,引发死锁(fatal error: all goroutines are asleep - deadlock!)。
推荐方案:用 sync.WaitGroup 安全关闭 channel
手动延时关闭(如 time.Sleep)不可靠且不精确;最佳实践是使用 sync.WaitGroup 等待所有发送者完成后再关闭 channel:
package main
import (
"fmt"
"sync"
)
func doStuff(s string, ch chan string, wg *sync.WaitGroup) {
defer wg.Done() // 确保 Done() 在函数退出时调用
ch <h3>关键注意事项</h3>
- ❌ 避免 for _, r := range results(results 是 string 类型)——这是对单个字符串的 rune 遍历,非 channel 消费;
- ✅ for s := range c 才是消费 channel 中所有 string 值的标准方式;
- ⚠️ 必须关闭 channel 才能使 for range c 终止,否则死锁;
- ? WaitGroup 比 time.Sleep 更精准、可扩展,尤其适用于动态数量的 goroutine;
- ? 循环次数无需借助数组:for i := 0; i
通过理解 channel 的生命周期管理与字符串遍历的本质差异,即可彻底规避此类低级但高频的并发陷阱。











