贝利信息

Golang使用channel进行并发通信示例

日期:2026-01-08 00:00 / 作者:P粉602998670
不加 default 的 select 会永久阻塞,因它必须等待至少一个 case 就绪;若所有 channel 均不可读写且无 default,则 goroutine 陷入死锁。

为什么 select 里不加 default 可能导致 goroutine 永久阻塞

Go 的 channel 是带缓冲或无缓冲的通信管道,但它的阻塞行为常被误判。比如向一个无缓冲 channel 发送数据时,若没有 goroutine 同时在另一端接收,send 操作会一直挂起——这本身是设计使然,但容易在逻辑分支中被忽略。

常见错误场景:多个 channel 等待响应,但没考虑“所有 channel 都暂不可用”的情况。

ch := make(chan int)
go func() {
    time.Sleep(2 * time.Second)
    ch <- 42 // 这里会阻塞,因为主 goroutine 还没开始 recv
}()
// 主 goroutine 如果直接 <-ch,没问题;但如果它先做别的事、再 select 且没 default,就可能卡死

如何安全关闭 channel 并避免 panic: send on closed channel

channel 关闭后不能再发送,但可以继续接收(已缓存的数据 + 零值)。错误常出现在多生产者场景下:谁该关?何时关?关早了其他 goroutine 还在发,就 panic。

原则:**只由发送方关闭,且确保所有发送操作已完成**。推荐用 sync.WaitGroup 协调。

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 表示“只能发送”, 表示“只能接收”。这不是语法糖,而是编译期检查机制,用错会报错。

典型用途:限制函数职责,防止意外写入或读取,提升接口安全性。

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 实现超时控制,但别直接传给多个 goroutine

time.After 返回一个单次触发的 ,常用于超时判断。但它不是“可重用资源”——每次调用才新建 channel,重复使用旧的 channel 会导致超时逻辑失效。

错误模式:把同一个 time.After(1*time.Second) 结果传给多个 select,第一个触发后 channel 就已读空,后续 select 永远等不到它。

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 的复用,这些得靠结构设计来保障。