Go 语言并发:goroutine + channel + sync.WaitGroup

Choyeon· 2026年9月8日· 2 分钟阅读· 447 阅读· 559 字· 1,909 字符· 更新于 2026年10月1日
Go 语言并发:goroutine + channel + sync.WaitGroup

Go 最引以为豪的特性就是原生并发模型:"不要通过共享内存通信,而要通过通信共享内存"。goroutine + channel 的 CSP 模式让并发代码可读性和安全性大幅提升。

goroutine 与工作池模式

一个 goroutine 初始栈仅 2KB,可以轻松启动十万级别。通过带缓冲 channel 实现固定大小 worker pool,避免无限创建导致调度压力,用 sync.WaitGroup 等待所有 worker 退出。

package main

import (
    "context"
    "fmt"
    "log"
    "sync"
    "time"
)

type Job struct{ ID int; URL string }
type Result struct{ JobID int; Latency time.Duration; Status int; Err error }

func worker(ctx context.Context, id int, jobs <-chan Job, results chan<- Result, wg *sync.WaitGroup) {
    defer wg.Done()
    log.Printf("[worker %d] started", id)
    for {
        select {
        case <-ctx.Done():
            log.Printf("[worker %d] cancelled: %v", id, ctx.Err())
            return
        case job, ok := <-jobs:
            if !ok { return }
            t0 := time.Now()
            select {
            case <-ctx.Done():
                results <- Result{JobID: job.ID, Err: ctx.Err()}
                return
            case <-time.After(500 * time.Millisecond):
                results <- Result{JobID: job.ID, Latency: time.Since(t0), Status: 200}
            }
        }
    }
}

func main() {
    const (
        workerCount = 4
        jobCount    = 20
    )
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()
    jobs := make(chan Job, workerCount*2)
    results := make(chan Result, workerCount*2)
    var wg sync.WaitGroup
    for i := 1; i <= workerCount; i++ {
        wg.Add(1)
        go worker(ctx, i, jobs, results, &wg)
    }
    go func() {
        for i := 1; i <= jobCount; i++ {
            jobs <- Job{ID: i, URL: fmt.Sprintf("https://api.example.com/item/%d", i)}
        }
        close(jobs)
    }()
    go func() { wg.Wait(); close(results) }()
    total := 0
    for r := range results {
        total++
        if r.Err != nil { fmt.Printf("[job %02d] ERROR %v\n", r.JobID, r.Err) }
        fmt.Printf("[job %02d] status=%d latency=%-8s total=%d\n", r.JobID, r.Status, r.Latency, total)
    }
    fmt.Printf("Done: %d/%d jobs\n", total, jobCount)
}

Context 取消与陷阱

context.WithTimeout/WithCancel 同时关闭所有嵌套协程,是防止泄漏的关键。常见陷阱:for-range 读已关闭 channel 自动退出、select 加 default 变成忙循环、无缓冲 channel 发送端阻塞造成 goroutine 悬挂。

特性 Go goroutine+channel Java Thread+Queue Rust async+tokio
最小开销 ~2KB栈 ~1MB栈 结构体级
启动10万耗时 <100ms 数秒/OOM ~200ms
同步原语 channel/mutex/Cond BlockingQueue/锁 tokio::sync::*
取消机制 Context统一 Future.cancel/中断 Drop/select!
常见陷阱 goroutine泄漏/阻塞 虚假唤醒/死锁 Pin! 生命周期

最佳实践

发送端 close channel,不要接收端关。优先使用有界缓冲 channel,避免无界队列导致内存暴涨。用 defer 覆盖所有关闭路径,防止异常下 goroutine 泄漏。

本文作者

评论 (0)

暂无评论,来抢沙发吧。