Day 12|channel 基础(通信与同步原语)[Go 语言 30 天系统学习计划]

学习目标

  • 理解 channel 在 Go 并发模型中的定位
  • 掌握无缓冲 channel 与有缓冲 channel 的行为差异
  • 搞清楚 channel 的阻塞语义
  • 避免 close、读写 channel 的常见错误

一、为什么 Go 需要 channel

在 Go 中,并发并不是靠“共享内存 + 锁”作为第一选择,而是:

通过通信来共享数据,而不是通过共享数据来通信

channel 正是 Go 并发模型中的核心通信机制。

channel 解决的不是“并发执行”,而是:

  • goroutine 之间的数据传递
  • goroutine 之间的同步与协作

工程认知:goroutine 负责“并发执行”,channel 负责“安全协作”。


二、channel 的基本定义与使用

定义一个 channel:

ch := make(chan int)

发送数据:

ch <- 10

接收数据:

v := <-ch

channel 是强类型的:

  • chan int 只能传 int
  • 编译期就能发现类型错误

三、无缓冲 channel(同步语义)

无缓冲 channel:

ch := make(chan int)

行为规则:

  • 发送操作会阻塞,直到有接收者
  • 接收操作会阻塞,直到有发送者

示例:

func main() {
    ch := make(chan int)

    go func() {
        ch <- 1
        fmt.Println("send done")
    }()

    v := <-ch
    fmt.Println("recv:", v)
}

输出顺序一定是:

send done
recv: 1

结论:无缓冲 channel 天然具备“同步点”的语义。


四、有缓冲 channel(异步语义)

定义有缓冲 channel:

ch := make(chan int, 2)

行为规则:

  • 缓冲未满:发送不阻塞
  • 缓冲已满:发送阻塞
  • 缓冲非空:接收不阻塞
  • 缓冲为空:接收阻塞

示例:

func main() {
    ch := make(chan int, 2)

    ch <- 1
    ch <- 2

    fmt.Println("send finished")

    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

说明:

  • 前两个 send 不会阻塞
  • 第三个 send 会阻塞(如果存在)

五、channel 的阻塞本质

channel 的阻塞并不是“CPU 忙等”,而是:

  • goroutine 被 runtime 挂起
  • 让出执行权给其他 goroutine
  • 条件满足后被重新唤醒

工程结论:channel 阻塞是高效的,但逻辑阻塞依然会导致 goroutine 泄漏。


六、channel 的关闭(close)语义

关闭 channel:

close(ch)

核心规则(必须记住):

  • 只能关闭发送方负责的 channel
  • 关闭一个已经关闭的 channel 会 panic
  • 向已关闭的 channel 发送数据会 panic
  • 从已关闭的 channel 接收数据是安全的

七、从关闭的 channel 接收数据

示例:

v, ok := <-ch

含义:

  • ok == true:正常接收到数据
  • ok == false:channel 已关闭且缓冲已空

常见用法:

for v := range ch {
    fmt.Println(v)
}

说明:

  • range 会一直读,直到 channel 被关闭
  • 这是消费者模型中最常见的写法

八、谁来关闭 channel(工程共识)

规则:

  • 发送方关闭 channel
  • 接收方不关闭 channel
  • 多个发送方时,通常由“协调者”关闭

错误示例:

close(ch) // 接收方关闭,极易 panic

工程结论:channel 的关闭是一种“广播信号”,不是资源释放。


九、channel 的常见错误总结

  • 向 nil channel 发送或接收(永久阻塞)
  • 忘记关闭 channel,导致 goroutine 泄漏
  • 重复关闭 channel(panic)
  • 多个发送方随意 close channel
  • 用 channel 当普通队列,不考虑阻塞语义

十、channel 的工程定位总结

channel 适合:

  • goroutine 之间的通信
  • 任务协作、流水线
  • 信号通知(完成、退出、取消)

channel 不适合:

  • 高频随机读写的大量共享数据
  • 复杂对象状态管理(更适合 mutex)

一句话总结:

channel 是用来“协作”的,不是用来“存数据”的。


预告:Day 13|select 与多路复用

下一天将系统讲解:

  • select 的执行规则
  • 多 channel 协调
  • 超时与 default 分支
  • 如何避免 channel 死锁

发表评论