电脑帮手
柔彩主题三 · 更轻盈的阅读体验

Go并发中缓冲channel的5个实用场景

发布时间:2026-04-17 11:31:00 阅读:2 次

Go程序时,channel是协程间通信的主力。无缓冲channel像一根细水管,两边必须同时准备好才能传数据;而缓冲channel就像带水箱的管道,能暂存几条消息,让生产者和消费者节奏错开——这在真实项目里特别管用。

场景1:批量日志收集

后端服务每秒产生几十条日志,直接写磁盘太慢,还可能拖垮主逻辑。开个缓冲channel(比如容量100),所有goroutine往里塞日志,另起一个goroutine定时从channel取一批写入文件:

logCh := make(chan string, 100)
// 日志生产者
go func() {
    for i := 0; i < 1000; i++ {
        logCh <- fmt.Sprintf("[INFO] request %d done", i)
    }
}()
// 日志消费者
go func() {
    batch := make([]string, 0, 50)
    for log := range logCh {
        batch = append(batch, log)
        if len(batch) >= 50 {
            writeToFile(batch)
            batch = batch[:0]
        }
    }
    if len(batch) > 0 {
        writeToFile(batch)
    }
}()

场景2:限流任务分发

调用第三方API有每秒10次限制,但内部请求可能瞬间涌来50个。用容量为10的缓冲channel做“漏斗”,配合time.Ticker控制消费节奏:

taskCh := make(chan Task, 10)
// 所有任务先入队
go func() {
    for _, t := range allTasks {
        taskCh <- t // 不阻塞,只要没满就行
    }
    close(taskCh)
}()
// 每100ms取一个执行
ticker := time.NewTicker(100 * time.Millisecond)
for range ticker.C {
    select {
    case task, ok := <-taskCh:
        if !ok { return }
        callExternalAPI(task)
    default:
        // 队列空了就退出
        return
    }
}

场景3:异步通知不丢消息

用户注册成功后要发邮件、推站内信、更新统计,三个动作耗时不同。用缓冲channel接收通知事件,即使某个下游暂时卡住,也不会让注册接口超时:

notifyCh := make(chan Notification, 20)
// 注册主流程只管发
func registerUser() {
    // ...创建用户逻辑
    notifyCh <- Notification{UserID: 123, Type: "welcome"}
}
// 后台慢慢处理
go func() {
    for n := range notifyCh {
        sendEmail(n)
        sendPush(n)
        updateStats(n)
    }
}()

场景4:防止goroutine爆炸

遍历1000个URL做健康检查,如果每个都起goroutine又不加控制,瞬间几百个协程可能压垮本地网络栈。用缓冲channel当“协程池”入口:

urlCh := make(chan string, 10) // 最多缓存10个待检URL
// 控制并发数为5
for i := 0; i < 5; i++ {
    go func() {
        for url := range urlCh {
            checkHealth(url)
        }
    }()
}
// 发送URL,超10个会阻塞,自然限速
for _, u := range urls {
    urlCh <- u
}
close(urlCh)

场景5:解耦读写速度差异

传感器每毫秒上报一次温度数据,但数据库写入平均要3毫秒。无缓冲channel会立刻堵死采集goroutine。换成容量100的缓冲channel,采集方几乎不等待,数据在内存里暂存几秒再落库:

tempCh := make(chan float64, 100)
// 采集goroutine(快)
go func() {
    for {
        t := readSensor()
        tempCh <- t // 几乎不阻塞
        time.Sleep(time.Millisecond)
    }
}()
// 写库goroutine(慢)
go func() {
    for t := range tempCh {
        saveToDB(t) // 可能花几毫秒
    }
}()

缓冲channel不是万能胶,容量设太大容易吃光内存,设太小又起不到缓冲作用。常见做法是根据峰值QPS × 平均处理延迟粗略估算,比如每秒100请求、单次处理200ms,缓冲10~20就比较稳。