贝利信息

Go并发编程中channel怎么用_Go通道通信机制讲解

日期:2026-01-24 00:00 / 作者:P粉602998670
必须用make初始化channel才能发送或接收,nil channel操作会panic;无缓冲channel(make(chan T)或make(chan T, 0))同步阻塞,缓冲channel异步通信,容量不能为负。

channel 必须初始化才能发送或接收

声明一个 chan int 变量只是创建了 nil channel,对它做 sendrecv 会立即 panic:"panic: send on nil channel" 或 "fatal error: all goroutines are asleep - deadlock"。必须用 make 初始化,

并指定缓冲区大小(0 表示无缓冲)。

常见写法:

ch := make(chan int)          // 无缓冲,同步通信
ch := make(chan string, 10)   // 缓冲容量为 10,异步通信

注意:make(chan T)make(chan T, 0) 效果相同,但后者语义更明确;缓冲区大小不能为负,传负数会 panic。

无缓冲 channel 的阻塞行为决定协程协作节奏

无缓冲 channel 的 操作是同步的:发送方会阻塞,直到有接收方就绪;接收方也一样。这天然适合“等待完成”“配对协作”场景,比如启动 goroutine 后等它结束:

done := make(chan bool)
go func() {
    // 做一些工作
    time.Sleep(100 * time.Millisecond)
    done <- true // 发送完成信号
}()
<-done // 主 goroutine 阻塞在此,直到收到信号

容易踩的坑:

range channel 要求 channel 关闭,否则永远阻塞

for v := range ch 本质是持续接收,直到 channel 关闭且缓冲区为空。如果没人调用 close(ch),循环永远不会退出——即使所有发送者已退出,只要 channel 没关,range 就卡住。

典型模式是“扇出-扇入”(fan-out/fan-in):

func fanIn(chs ...<-chan int) <-chan int {
    out := make(chan int)
    for _, ch := range chs {
        go func(c <-chan int) {
            for v := range c { // 这里依赖每个 c 被关闭
                out <- v
            }
        }(ch)
    }
    go func() {
        close(out) // 所有子 goroutine 结束后才关 out
    }()
    return out
}

关键点:

select + timeout 是避免 channel 永久阻塞的标准解法

生产环境几乎不会让 goroutine 在 channel 上无限等待。用 select 配合 time.Aftercontext.WithTimeout 实现超时控制:

select {
case v := <-ch:
    fmt.Println("received:", v)
case <-time.After(2 * time.Second):
    fmt.Println("timeout")
}

注意:

channel 的核心不是“传递数据”,而是“协调执行时机”。理解阻塞点在哪、谁负责关闭、如何设防超时,比记住语法更重要。