Go并发模型的核心:goroutine与channel
Go语言的并发模型基于CSP(Communicating Sequential Processes)理论,goroutine是轻量级用户态线程,channel是goroutine之间的通信管道。一个goroutine的栈内存初始仅2KB,可按需扩展至1GB,单机轻松调度百万级goroutine。
高并发场景下的核心问题不是如何创建更多goroutine,而是如何安全地编排并发任务、控制并发数、处理错误传播和资源回收。以下从实际案例出发,拆解三种常用的并发编排模式。
模式一:worker pool控制并发数
当需要处理大量任务时,无限制创建goroutine会导致内存暴涨和调度开销陡增。Worker pool模式通过固定数量的worker goroutine从任务channel中消费任务,实现并发数的精确控制:
func ProcessTasks(tasks []Task, workerCount int) []Result {
taskCh := make(chan Task, len(tasks))
resultCh := make(chan Result, len(tasks))
var wg sync.WaitGroup
for i := 0; i < workerCount; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for task := range taskCh {
resultCh <- process(task)
}
}()
}
for _, t := range tasks {
taskCh <- t
}
close(taskCh)
go func() {
wg.Wait()
close(resultCh)
}()
var results []Result
for r := range resultCh {
results = append(results, r)
}
return results
}
关键要点:taskCh的缓冲区大小设为任务总数,避免投递goroutine阻塞;worker通过range taskCh自动感知channel关闭退出;resultCh的关闭放在独立goroutine中,避免wg.Wait与resultCh消费形成死锁。
模式二:errgroup实现错误传播
在并发任务中,某个子任务失败时需要取消其他正在执行的子任务。标准库golang.org/x/sync/errgroup提供了这一能力:
func FetchAll(ctx context.Context, urls []string) ([]string, error) {
g, ctx := errgroup.WithContext(ctx)
results := make([]string, len(urls))
for i, url := range urls {
i, url := i, url
g.Go(func() error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
data, err := fetch(ctx, url)
if err != nil {
return fmt.Errorf("fetch %s: %w", url, err)
}
results[i] = data
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return results, nil
}
errgroup.WithContext返回的ctx会在任意子任务返回错误时被取消,其他子任务通过select感知ctx.Done()后提前退出。这比手动传递cancel函数更加简洁和安全。
模式三:fan-out/fan-in实现分布式归并
当单个任务的处理结果需要分发给多个下游消费者(fan-out),或多个上游生产者的结果需要归并到一个输出流(fan-in)时,可以用channel组合实现:
func FanIn(channels ...<-chan Data) <-chan Data {
out := make(chan Data)
var wg sync.WaitGroup
for _, ch := range channels {
wg.Add(1)
go func(c <-chan Data) {
defer wg.Done()
for data := range c {
out <- data
}
}(ch)
}
go func() {
wg.Wait()
close(out)
}()
return out
}
fan-out/fan-in模式在日志归集、数据管道等场景中非常实用。例如将多个微服务的日志流fan-in到一个聚合channel,再由统一的存储writer写入时序数据库。
并发安全的常见陷阱
高并发设计中容易踩到的坑:
1. goroutine泄漏:消费者goroutine因channel未关闭而永远阻塞。确保每个channel都有对应的关闭逻辑,且关闭操作只执行一次。
2. 竞态条件:多个goroutine同时写map或slice。使用go test -race检测竞态,必要时用sync.Mutex或sync.Map替代。
3. context传播断裂:在RPC调用或HTTP请求中未传递ctx,导致超时控制失效。所有涉及I/O的函数都应接受context参数。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/go-yu-yan-gao-bing-fa-she-ji-ji-yu-channel-yu-errgroup-de/