发布于2026-07-05 阅读(0)
扫一扫,手机访问
在 Go 并发编程里,channel 是个好东西,但用起来也容易踩坑。比如说,从 string 类型的 channel 接收数据时,不少人会误用 for range 去遍历字符串本身,结果逐 rune 输出,完全偏离了初衷。下面就来聊聊这个问题,并给出一个基于 sync.WaitGroup 的健壮方案。

先搞清楚基本概念:chan string 传输的是完整的字符串值,不是字符流。比如下面这个 doStuff 函数,它把整个 "helloooo" 作为一个值发送到 channel,这是正确的操作:
func doStuff(s string, ch chan string) {
ch <- s // ✅ 正确:发送整个字符串
}
问题往往出在接收端。看这段典型错误代码:
results := <-c // ❌ 只接收一个字符串值
fmt.Println("channel size = ", len(results)) // 打印 "helloooo" 的字节长度(8)
for _, r := range results { // ❌ range 字符串 → 遍历每个 rune
fmt.Println(string(r)) // 输出 'h', 'e', 'l', 'l', 'o', 'o', 'o', 'o'
}
这里 results 是一个 string 变量,len(results) 返回的是 UTF-8 字节数(8),而 for range results 实际上在遍历字符串的 Unicode 码点(rune),自然而然就逐字符输出了。猜猜看,问题出在哪?——本质上是把“从 channel 读一个值”和“遍历这个值的内部结构”搞混了。
✅ 正确做法其实很简单:用 for range c 从 channel 中持续接收多个值:
for s := range c {
fmt.Println(s) // 每次输出一个完整字符串:"helloooo"
}
不过有个关键前提:for range 在 channel 上会阻塞等待新值,直到 channel 被关闭。如果不关闭 channel,程序就会死锁(fatal error: all goroutines are asleep - deadlock!)。
这是 Go 并发编程的标准实践——确保所有 sender 完成后再关闭 channel:
package main
import (
"fmt"
"sync"
)
func doStuff(s string, ch chan string, wg *sync.WaitGroup) {
defer wg.Done()
ch <- s
}
func main() {
c := make(chan string)
var wg sync.WaitGroup
// 启动 5 个 goroutine 发送字符串
for i := 0; i < 5; i++ {
wg.Add(1)
go doStuff("helloooo", c, &wg)
}
// 启动 goroutine:等待所有发送完成,然后关闭 channel
go func() {
wg.Wait()
close(c)
}()
// 安全接收所有值
for s := range c {
fmt.Println("Received:", s)
}
}
输出示例:
Received: helloooo Received: helloooo Received: helloooo Received: helloooo Received: helloooo
说到底,掌握 channel 的“值语义”与“生命周期管理”,才是写出健壮 Go 并发程序的关键一步。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8