在Golang的并发模型里,channel是goroutine之间通信的主要手段。当业务需要同时和多个channel打交道时,如果处理方式不当,程序可能出现阻塞、漏收数据或者goroutine泄露。下面汇总几种在项目中常用的多channel交互方法。
使用select监听多个channel
select可以让一个goroutine同时等待多个channel操作,哪个case就绪就执行哪个,类似网络编程里的多路复用。
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "from ch1"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "from ch2"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}
}
扇出与扇入模式
扇出指启动多个goroutine处理同一个channel的数据,扇入指把多个channel的数据合并到一个channel,方便统一消费。
package main
import (
"fmt"
"sync"
)
func worker(id int, in <-chan int, out chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for v := range in {
out <- v * id
}
}
func main() {
in := make(chan int, 10)
out := make(chan int, 10)
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go worker(i, in, out, &wg)
}
for i := 1; i <= 5; i++ {
in <- i
}
close(in)
go func() {
wg.Wait()
close(out)
}()
for v := range out {
fmt.Println(v)
}
}
用context控制多个channel生命周期
当多个goroutine各自持有channel时,可以用context统一发取消信号,避免 goroutine 一直阻塞在发送或接收上。
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
ch := make(chan int)
go func() {
for {
select {
case <-ctx.Done():
fmt.Println("worker exit")
return
case v := <-ch:
fmt.Println("recv", v)
}
}
}()
time.Sleep(time.Second)
cancel()
time.Sleep(time.Second)
}
结合WaitGroup等待任务结束
如果有多个生产者向不同channel写数据,主流程可用sync.WaitGroup等待它们全部完成,再安全关闭channel。
| 方法 | 适用场景 |
|---|---|
| select | 单goroutine监听多channel |
| 扇出扇入 | 并行处理并汇总结果 |
| context | 统一取消和超时控制 |
| WaitGroup | 等待一组goroutine结束 |
实际开发中,这些方式经常组合使用。比如用context控制退出,用WaitGroup等所有写goroutine关闭channel,再用select消费。理解每种方法的特点,就能在面对复杂并发流程时选对工具。