Golang如何实现生产者消费者模型_Go语言并发模型实战

不能直接 close(channel) 后继续 send,否则触发 panic: send on closed channel;生产者需在完成任务后仅 close 一次,消费者须用 for v, ok := range ch 或 for { select { case v, ok :=

Go 用 channel + goroutine 实现生

产者消费者模型,核心是避免手动加锁、不依赖第三方库,但必须注意缓冲区容量和关闭时机,否则容易死锁或 panic。

为什么不能直接 close(channel) 后继续 send

向已关闭的 chan 发送数据会触发 panic:panic: send on closed channel。生产者退出前若未协调好关闭顺序,消费者还在读,就极易出错。

  • 生产者完成任务后应调用 close(ch),仅此一次
  • 消费者必须用 for v, ok := 循环读取,靠 ok 判断 channel 是否关闭
  • 不要在多个生产者中随意 close —— 只有最后一个完成的生产者才该关

带缓冲的 channel 和无缓冲 channel 性能差异明显

无缓冲 chan int 要求生产者和消费者严格同步(send 和 receive 必须同时就绪),适合强顺序控制;带缓冲如 make(chan int, 100) 允许短暂解耦,吞吐更高,但缓冲区过大会掩盖背压问题。

  • 缓冲大小建议设为预期峰值并发量 × 单次批处理量,例如日志采集场景常用 make(chan *LogEntry, 1024)
  • 若消费者处理慢、缓冲满,生产者 goroutine 会阻塞在 ch ,这是天然背压机制,别急着加超时或丢弃逻辑
  • len(ch) 查当前队列长度(非缓冲区容量),可用于监控积压情况

如何安全支持多个生产者 + 多个消费者

多生产者共用一个 channel 没问题,但关闭需额外同步;多个消费者可并行读同一 channel,无需额外锁。

  • sync.WaitGroup 管理所有生产者完成信号,由主 goroutine 统一 close
  • 消费者数量不建议动态伸缩 —— 启动时固定启动 N 个 go consumer(ch) 即可
  • 若需优雅退出(如收到 os.Interrupt),应通过额外的 done chan struct{} 通知所有 goroutine,而非直接 close 工作 channel
package main

import ( "fmt" "sync" "time" )

func main() { ch := make(chan int, 5) var wg sync.WaitGroup

// 启动 2 个生产者
for i := 0; i < 2; i++ {
    wg.Add(1)
    go func(id int) {
        defer wg.Done()
        for j := 0; j < 3; j++ {
            ch <- id*10 + j
            time.Sleep(100 * time.Millisecond)
        }
    }(i)
}

// 启动 3 个消费者
for i := 0; i < 3; i++ {
    go func(id int) {
        for v := range ch {
            fmt.Printf("consumer %d got %d\n", id, v)
            time.Sleep(200 * time.Millisecond)
        }
    }(i)
}

// 等待所有生产者结束,再关闭 channel
go func() {
    wg.Wait()
    close(ch)
}()

// 主 goroutine 不退出,等消费者自然结束
time.Sleep(3 * time.Second)

}

真正难的不是写通这个模型,而是判断什么时候该用带缓冲 channel、什么时候该加超时、以及如何把「关闭 channel」这件事从业务逻辑里剥离开 —— 这些细节没处理好,上线后就是偶发 panic 或 goroutine 泄漏。