不加 default 的 select 会永久阻塞,因它必须等待至少一个 case 就绪;若所有 channel 均不可读写且无 default,则 goroutine 陷入死锁。
select 里不加 default 可能导致 goroutine 永久阻塞Go 的 channel 是带缓冲或无缓冲的通信管道,但它的阻塞行为常被误判。比如向一个无缓冲 channel 发送数据时,若没有 goroutine 同时在另一端接收,send 操作会一直挂起——这本身是设计使然,但容易在逻辑分支中被忽略。
常见错误场景:多个 channel 等待响应,但没考虑“所有 channel 都暂不可用”的情况。
send 和 recv 必须成对出现,否则至少一方永久等待make(chan int, 1))可暂存一个值,但缓冲满后仍会阻塞 send
select 中不写 default,就等同于“必须等到某个 case 就绪”,没有兜底逻辑ch := make(chan int)
go func() {
time.Sleep(2 * time.Second)
ch <- 42 // 这里会阻塞,因为主 goroutine 还没开始 recv
}()
// 主 goroutine 如果直接 <-ch,没问题;但如果它先做别的事、再 select 且没 default,就可能卡死
panic: send on closed channel
channel 关闭后不能再发送,但可以继续接收(已缓存的数据 + 零值)。错误常出现在多生产者场景下:谁该关?何时关?关早了其他 goroutine 还在发,就 panic。
原则:**只由发送方关闭,且确保所有发送操作已完成**。推荐用 sync.WaitGroup 协调。
false(如 v, ok := 中 ok==false)
close(ch)
range 遍历 channel,它会在 channel 关闭且缓冲为空时自动退出ch := make(chan int, 2)
go func() {
defer close(ch) // 正确:由 sender defer 关闭
ch <- 1
ch <- 2
}()
for v := range ch {
// 自动检测关闭,安全遍历
fmt.Println(v)
}
chan 和 类型的区别直接影响函数参数设计Go 的 channel 类型支持方向限定:chan 表示“只能发送”, 表示“只能接收”。这不是语法糖,而是编译期检查机制,用错会报错。
典型用途:限制函数职责,防止意外写入或读取,提升接口安全性。
,调用方传入普通 chan int 或 都合法(协变)
传给期望 chan 的参数(方向不匹配)
chan int 的函数,若只想暴露接收能力,应返回
func counter(out chan<- int) { // 只允许往 out 发送
for i := 0; i < 3; i++ {
out <- i
}
close(out)
}
func printer(in <-chan int) { // 只允许从 in 接收
for v := range in {
fmt.Println("got:", v)
}
}
time.After 配合 select 实现超时控制,但别直接传给多个 goroutinetime.After 返回一个单次触发的 ,常用于超时判断。但它不是“可重用资源”——每次调用才新建 channel,重复使用旧的 channel 会导致超时逻辑失效。
错误模式:把同一个 time.After(1*time.Second) 结果传给多个 select,第一个触发后 channel 就已读空,后续 select 永远等不到它。
select 块,应调用一次 time.After
time.NewTimer 并手动 Reset,但注意并发安全time.After,语义清晰且无状态管理负担ch := make(chan string, 1)
go func() {
time.Sleep(1500 * time.Millisecond)
ch <- "done"
}()
select {
case msg := <-ch:
fmt.Println("received:", msg)
case <-time.After(1 * time.Second): // 每次 select 都新建 timer
fmt.Println("timeout")
}
实际写并发逻辑时,最易被忽略的是 channel 的生命周期归属和方向控制——不是“能通就行”,而是“谁建、谁关、谁读、谁写”必须提前约定清楚。类型系统能拦住一部分错误,但关 channel 的时机、select 的兜底、超时 channel 的复用,这些得靠结构设计来保障。