Skip to content

Channel

底层 hchan、缓冲/非缓冲、关闭与 select、死锁场景。

Updated View as Markdown
For humans

Channel

Channel 是 Go 的通信原语(CSP):不要通过共享内存通信,通过通信共享内存。面试必考:底层结构、阻塞语义、死锁。

hchan 结构

hchan: 缓冲 + 双向等待队列

  • buf:环形缓冲(有缓冲 channel)
  • sendq / recvq:阻塞的发送者/接收者队列(双向链表)
  • mutex:所有操作加锁(channel 本身并发安全)
  • 内存模型:发送和接收有 happens-before 关系(发送完成 → 接收者看到)

缓冲 vs 非缓冲

类型 语义 阻塞
非缓冲 make(chan int) 同步:发必须有收 发送阻塞直到有人接收,反之亦然
缓冲 make(chan int, 3) 异步:积压 3 个 缓冲满才阻塞发送,空才阻塞接收
  • 非缓冲 channel 是同步点:发和收同时发生(两个 goroutine 会合)
  • 缓冲 channel 是队列:解耦生产消费节奏
  • 面试点:非缓冲 channel 发完必须有人收,否则死锁(见下)

关闭与取值

close(ch)              // 关闭: 不能再发送(发送 panic), 可继续接收
v, ok := <-ch          // 关闭且空: ok = false, v 是零值
  • 关闭原则:只在发送方关闭(接收方关闭会导致发送方 panic)
  • 关闭后接收:缓冲里的取完,取到零值 + ok=false(用于广播退出信号)
  • 发送到已关闭的 channel 会 panic:关闭和发送的并发要自己保证(谁 close 谁负责)

select:多路等待

select {
case v := <-ch1:
    // 处理 ch1
case <-time.After(2 * time.Second):
    // 超时
default:
    // 非阻塞(所有 case 都不可用)
}
  • select 同时等待多个 channel,随机选择就绪的 case(多个就绪时随机,防饿死)
  • 无 case 就绪且无 default:阻塞等待
  • 经典用法:超时控制、退出信号、非阻塞收发

死锁场景(必考)

场景 原因
主 goroutine 从无缓冲 channel 读,无人发 没有接收者/发送者,全部阻塞
主 goroutine 向无缓冲 channel 发,无人收 同上
两个 goroutine 互相等对方的 channel 循环等待
ch := make(chan int)
ch <- 1      // fatal error: all goroutines are asleep - deadlock!
  • 死锁是运行时检测的:所有 goroutine 都阻塞时,runtime 报 deadlock 并崩溃
  • 生产避免:明确 channel 方向(<-chan / chan<- 参数约束)、超时兜底、职责清晰

面试追问

  1. hchan 结构? 环形缓冲 + 发送/接收等待队列 + mutex。操作全加锁所以并发安全
  2. 缓冲和非缓冲的区别? 非缓冲是同步点(发必须有收),缓冲是队列(满才阻塞)
  3. 关闭后发送会怎样? panic。关闭后接收:取完缓冲拿零值 + ok=false。只在发送方关闭
  4. select 随机性? 多个 case 就绪随机选,防一个 case 饿死。配合 default 做非阻塞
  5. 死锁怎么避免? 明确 channel 方向、超时(select + time.After)、发送接收职责清晰。runtime 检测到全阻塞会报 deadlock
Navigation

Type to search…

↑↓ navigate↵ selectEsc close