Skip to content

Go 并发进阶完全指南:从 Context 到 errgroup 的工程模式 ​

Go 的并发模型是它最强大的武器。goroutine 足够轻量,但生产级并发还需要:Context 取消传播、超时控制、errgroup 优雅等待、worker pool 限流、fan-out 并发模式、channel pipeline 管道。本文从 Go 并发基础文章延伸,系统覆盖 Go 工程级并发的核心模式,是你构建高并发服务的必备手册。

本文由小虾子 🦐 撰写

Context:取消传播与超时控制 ​

Context 核心概念 ​

go
// Context 的三个核心能力:
// 1. 取消信号:主动取消正在进行的操作
// 2. 超时控制:限制操作的最长执行时间
// 3. 值传递:在 goroutine 之间传递请求级别的数据

import "context"

// ===== Context 层级 =====
func main() {
    // context.Background():根 Context,无法取消,用于 main() / 顶级初始化
    ctx := context.Background()

    // context.WithCancel:主动取消
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()

    // context.WithTimeout:超时取消
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()

    // context.WithDeadline:定时取消(绝对时间点)
    deadline := time.Now().Add(10 * time.Second)
    ctx, cancel := context.WithDeadline(ctx, deadline)
    defer cancel()

    // context.WithValue:携带值
    ctx := context.WithValue(ctx, "user_id", 123)
    userID := ctx.Value("user_id")  // 123
}

取消传播 ​

go
package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    // ===== 基础取消 =====
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    go func(ctx context.Context) {
        for {
            select {
            case <-ctx.Done():  // 收到取消信号
                fmt.Println("goroutine 收到取消信号,退出")
                return
            default:
                fmt.Println("工作中...")
                time.Sleep(500 * time.Millisecond)
            }
        }
    }(ctx)

    time.Sleep(2 * time.Second)
    fmt.Println("主函数发起取消")
    cancel()  // 发送取消信号
    time.Sleep(1 * time.Second)
}

// ===== HTTP 服务器中的 Context =====
/*
func handler(w http.ResponseWriter, r *http.Request) {
    // r.Context() 获取请求绑定的 Context
    // HTTP 服务器自动为每个请求创建带超时的 Context
    ctx := r.Context()

    select {
    case <-time.After(5 * time.Second):
        fmt.Fprintf(w, "处理完成")
    case <-ctx.Done():
        // 客户端断开了连接
        fmt.Printf("请求取消: %v\n", ctx.Err())
    }
}
*/

超时控制 ​

go
package main

import (
    "context"
    "fmt"
    "time"
)

func longRunningTask(ctx context.Context) error {
    // 模拟耗时操作
    select {
    case <-time.After(3 * time.Second):
        fmt.Println("任务完成")
        return nil
    case <-ctx.Done():
        return ctx.Err()  // 返回 context.Canceled 或 context.DeadlineExceeded
    }
}

func main() {
    // ===== 场景 1:数据库查询超时 =====
    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    defer cancel()

    err := longRunningTask(ctx)
    if err != nil {
        fmt.Printf("任务失败: %v\n", err)
        // context.DeadlineExceeded
    }

    // ===== 场景 2:HTTP 请求超时(使用 net/http)=====
    /*
    client := &http.Client{
        Timeout: 2 * time.Second,  // 整体超时
    }
    resp, err := client.Get("https://slow-api.example.com/data")
    */

    // ===== 场景 3:GRPC 超时传播 =====
    /*
    // 客户端设置超时
    ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
    defer cancel()

    // 超时自动传播到 gRPC 服务端
    resp, err := client.UserService().GetUser(ctx, &pb.GetUserRequest{Id: 1})
    */
}

值传递 ​

go
package main

import (
    "context"
    "fmt"
)

func main() {
    // ===== 追踪链(Trace ID)=====
    ctx := context.WithValue(context.Background(), "trace_id", "abc-123")

    // 在 goroutine 中获取
    go processRequest(ctx)

    // 也可以多层传递
    ctx = context.WithValue(ctx, "user_id", 42)
    go deepCall(ctx)
}

func processRequest(ctx context.Context) {
    traceID := ctx.Value("trace_id")
    fmt.Printf("trace_id: %v\n", traceID)
}

func deepCall(ctx context.Context) {
    traceID := ctx.Value("trace_id")  // 沿袭上层的 trace_id
    userID := ctx.Value("user_id")
    fmt.Printf("trace_id: %v, user_id: %v\n", traceID, userID)
}

// ===== 最佳实践:定义 Context Key 类型 =====
type contextKey string

const (
    TraceIDKey contextKey = "trace_id"
    UserIDKey  contextKey = "user_id"
)

func getTraceID(ctx context.Context) string {
    if v := ctx.Value(TraceIDKey); v != nil {
        return v.(string)
    }
    return ""
}

// 用自定义类型避免 key 冲突(推荐)

errgroup:优雅的并发等待 ​

errgroup 基础 ​

go
// golang.org/x/sync/errgroup
// errgroup = sync.WaitGroup + 错误收集 + Context 取消

import "golang.org/x/sync/errgroup"

// ===== 基础用法 =====
func fetchAll(urls []string) ([]string, error) {
    g := new(errgroup.Group)
    results := make([]string, len(urls))

    for i, url := range urls {
        i, url := i, url  // 捕获循环变量
        g.Go(func() error {
            resp, err := http.Get(url)
            if err != nil {
                return fmt.Errorf("请求 %s 失败: %w", url, err)
            }
            defer resp.Body.Close()

            body, err := io.ReadAll(resp.Body)
            if err != nil {
                return fmt.Errorf("读取 %s 失败: %w", url, err)
            }

            results[i] = string(body)
            return nil  // 成功
        })
    }

    // Wait 等待所有 goroutine 完成,并返回第一个非 nil 错误
    if err := g.Wait(); err != nil {
        return nil, err
    }
    return results, nil
}

errgroup + Context ​

go
// ===== errgroup.WithContext:自动取消 =====
func fetchAllWithContext(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 {
            // 这里不需要手动检查 ctx.Done()
            // 当 ctx 被取消时,g.Wait() 会立即返回 ctx.Err()
            // 已启动的 goroutine 继续运行,但结果会被忽略

            req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
            if err != nil {
                return err
            }

            resp, err := http.DefaultClient.Do(req)
            if err != nil {
                return fmt.Errorf("请求 %s 失败: %w", url, err)
            }
            defer resp.Body.Close()

            body, err := io.ReadAll(resp.Body)
            if err != nil {
                return fmt.Errorf("读取 %s 失败: %w", url, err)
            }

            results[i] = string(body)
            return nil
        })
    }

    if err := g.Wait(); err != nil {
        return nil, err  // 返回第一个错误
    }
    return results, nil
}

// ===== HTTP 服务中使用 =====
func handler(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    urls := []string{
        "https://httpbin.org/delay/1",
        "https://httpbin.org/delay/2",
        "https://httpbin.org/delay/3",
    }

    results, err := fetchAllWithContext(ctx, urls)
    if err != nil {
        // 客户端断开时,不返回 500,而是静默处理
        fmt.Printf("获取失败: %v\n", err)
        return
    }

    json.NewEncoder(w).Encode(results)
}

多层 errgroup ​

go
// ===== 两层 errgroup:并行 + 串行 =====
func fetchUsersAndTheirPosts(ctx context.Context, userIDs []int) (map[int][]Post, error) {
    g, ctx := errgroup.WithContext(ctx)
    results := make(map[int][]Post)

    mu := sync.Mutex{}  // 保护 results map

    for _, userID := range userIDs {
        userID := userID
        g.Go(func() error {
            // 获取用户信息
            user, err := fetchUser(ctx, userID)
            if err != nil {
                return fmt.Errorf("获取用户 %d 失败: %w", userID, err)
            }

            // 获取该用户的所有文章(串行获取)
            posts, err := fetchPostsByUser(ctx, user.ID)
            if err != nil {
                return fmt.Errorf("获取用户 %d 的文章失败: %w", userID, err)
            }

            mu.Lock()
            results[user.ID] = posts
            mu.Unlock()

            return nil
        })
    }

    if err := g.Wait(); err != nil {
        return nil, err
    }

    return results, nil
}

Worker Pool:并发控制 ​

固定数量 Worker ​

go
package main

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

// Job:任务接口
type Job interface {
    Do() error
}

// ImageJob:示例任务
type ImageJob struct {
    ID   int
    Path string
}

func (j *ImageJob) Do() error {
    // 模拟图片处理
    time.Sleep(500 * time.Millisecond)
    fmt.Printf("处理图片 %d: %s\n", j.ID, j.Path)
    return nil
}

// WorkerPool:固定数量 Worker 的并发池
type WorkerPool struct {
    jobs    chan Job
    results chan error
    wg      sync.WaitGroup
}

func NewWorkerPool(workers int, queueSize int) *WorkerPool {
    pool := &WorkerPool{
        jobs:    make(chan Job, queueSize),  // 有缓冲的任务队列
        results: make(chan error, queueSize),
    }

    // 启动固定数量的 Worker
    for i := 0; i < workers; i++ {
        pool.wg.Add(1)
        go pool.worker(i)
    }

    return pool
}

func (p *WorkerPool) worker(id int) {
    defer p.wg.Done()
    for job := range p.jobs {  // 从队列取任务(阻塞)
        err := job.Do()
        p.results <- err
    }
    fmt.Printf("Worker %d 退出\n", id)
}

func (p *WorkerPool) Submit(job Job) {
    p.jobs <- job  // 发送到队列(队列满则阻塞)
}

func (p *WorkerPool) Shutdown() {
    close(p.jobs)   // 关闭任务队列
    p.wg.Wait()    // 等待所有 Worker 退出
    close(p.results)
}

func main() {
    // 3 个 Worker,队列最多 100 个任务
    pool := NewWorkerPool(3, 100)

    // 提交 10 个任务
    for i := 1; i <= 10; i++ {
        pool.Submit(&ImageJob{
            ID:   i,
            Path: fmt.Sprintf("/images/img-%d.jpg", i),
        })
    }

    // 关闭池
    pool.Shutdown()

    fmt.Println("所有任务完成")
}

动态 Worker Pool(信号量模式) ​

go
// ===== 动态并发:限制同时运行的 goroutine 数量 =====

import (
    "context"
    "golang.org/x/sync/semaphore"
    "sync"
)

// SemaphoreWorkerPool:基于信号量的 Worker Pool
type SemaphorePool struct {
    sem     *semaphore.Weighted  // 信号量
    timeout time.Duration
}

func NewSemaphorePool(maxConcurrent int, timeout time.Duration) *SemaphorePool {
    return &SemaphorePool{
        sem:     semaphore.NewWeighted(int64(maxConcurrent)),
        timeout: timeout,
    }
}

func (p *SemaphorePool) Do(ctx context.Context, fn func() error) error {
    // 获取信号量(最多 maxConcurrent 个并发)
    // 如果信号量已满,这里会阻塞
    // 绑定 ctx,可以在 ctx 取消时提前退出等待
    if err := p.sem.Acquire(ctx, 1); err != nil {
        return fmt.Errorf("获取信号量失败: %w", err)
    }
    defer p.sem.Release(1)

    // 执行带超时的任务
    ctx, cancel := context.WithTimeout(ctx, p.timeout)
    defer cancel()

    done := make(chan error, 1)
    go func() {
        done <- fn()
    }()

    select {
    case err := <-done:
        return err
    case <-ctx.Done():
        return ctx.Err()
    }
}

// ===== 批量处理中的限流 =====
func processBatch(ctx context.Context, items []Item) error {
    pool := NewSemaphorePool(10, 30*time.Second)  // 最多 10 并发

    g, ctx := errgroup.WithContext(ctx)

    for _, item := range items {
        item := item
        g.Go(func() error {
            return pool.Do(ctx, func() error {
                return processItem(ctx, item)
            })
        })
    }

    return g.Wait()
}

并发爬虫实战 ​

go
// ===== 完整并发爬虫 =====
import (
    "context"
    "fmt"
    "golang.org/x/sync/errgroup"
    "golang.org/x/sync/semaphore"
    "net/http"
    "sync"
    "time"
)

type Crawler struct {
    client    *http.Client
    sem       *semaphore.Weighted  // 并发限制
    visited   sync.Map            // 已访问 URL(并发安全)
    rateLimit time.Duration       // 速率限制
    mu        sync.Mutex
    lastReq   time.Time
}

func NewCrawler(maxConcurrent int, rateLimit time.Duration) *Crawler {
    return &Crawler{
        client: &http.Client{
            Timeout: 10 * time.Second,
        },
        sem:       semaphore.NewWeighted(int64(maxConcurrent)),
        rateLimit: rateLimit,
    }
}

func (c *Crawler) Crawl(ctx context.Context, startURL string, depth int) (map[string]string, error) {
    results := make(map[string]string)

    g, ctx := errgroup.WithContext(ctx)

    // BFS 遍历
    urls := []string{startURL}
    visited := make(map[string]bool)
    var mu sync.Mutex

    for depth > 0 {
        nextLevel := make([]string, 0)

        for _, url := range urls {
            url := url

            // 检查是否已访问
            mu.Lock()
            if visited[url] {
                mu.Unlock()
                continue
            }
            visited[url] = true
            mu.Unlock()

            g.Go(func() error {
                // 获取信号量(限制并发)
                if err := c.sem.Acquire(ctx, 1); err != nil {
                    return nil  // ctx 取消时优雅退出
                }
                defer c.sem.Release(1)

                // 速率限制
                c.throttle()

                // 抓取页面
                body, newURLs, err := c.fetch(ctx, url)
                if err != nil {
                    return nil  // 单个失败不影响整体
                }

                results[url] = body

                mu.Lock()
                for _, u := range newURLs {
                    if !visited[u] {
                        nextLevel = append(nextLevel, u)
                    }
                }
                mu.Unlock()

                return nil
            })
        }

        if err := g.Wait(); err != nil {
            return results, err
        }

        urls = nextLevel
        depth--
    }

    return results, nil
}

func (c *Crawler) fetch(ctx context.Context, url string) (string, []string, error) {
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return "", nil, err
    }
    req.Header.Set("User-Agent", "Go Crawler/1.0")

    resp, err := c.client.Do(req)
    if err != nil {
        return "", nil, err
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    // 实际应解析 HTML 提取链接
    newURLs := extractLinks(string(body))

    return string(body), newURLs, nil
}

func (c *Crawler) throttle() {
    c.mu.Lock()
    defer c.mu.Unlock()

    elapsed := time.Since(c.lastReq)
    if elapsed < c.rateLimit {
        time.Sleep(c.rateLimit - elapsed)
    }
    c.lastReq = time.Now()
}

// 使用
func main() {
    crawler := NewCrawler(
        maxConcurrent: 5,
        rateLimit:      200*time.Millisecond,  // 每秒最多 5 个请求
    )

    ctx := context.Background()
    results, err := crawler.Crawl(ctx, "https://example.com", 2)
    if err != nil {
        fmt.Printf("爬取出错: %v\n", err)
    }
    fmt.Printf("爬取了 %d 个页面\n", len(results))
}

Fan-Out / Fan-In 模式 ​

Fan-Out:并发分发 ​

go
// ===== Fan-Out:1 个任务 → N 个 Worker 并发处理 =====
func fanOutExample() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)

    // Fan-Out:启动 5 个 Worker
    numWorkers := 5
    for w := 1; w <= numWorkers; w++ {
        go worker(w, jobs, results)
    }

    // 发送任务
    go func() {
        for i := 1; i <= 20; i++ {
            jobs <- i
        }
        close(jobs)
    }()

    // 收集结果
    for r := range results {
        fmt.Printf("结果: %d\n", r)
    }
}

func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        // 模拟处理
        time.Sleep(100 * time.Millisecond)
        results <- job * 2
    }
}

Fan-In:合并结果 ​

go
// ===== Fan-In:合并多个 channel 到 1 个 =====
import "sync"

func fanIn(inputs ...<-chan int) <-chan int {
    out := make(chan int)
    var wg sync.WaitGroup

    for _, ch := range inputs {
        wg.Add(1)
        go func(c <-chan int) {
            defer wg.Done()
            for v := range c {
                out <- v
            }
        }(ch)
    }

    go func() {
        wg.Wait()
        close(out)
    }()

    return out
}

// 或者用 select 多路复用
func fanInSelect(ch1, ch2 <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for {
            select {
            case v, ok := <-ch1:
                if !ok {
                    ch1 = nil
                } else {
                    out <- v
                }
            case v, ok := <-ch2:
                if !ok {
                    ch2 = nil
                } else {
                    out <- v
                }
            }
            if ch1 == nil && ch2 == nil {
                close(out)
                return
            }
        }
    }()
    return out
}

Channel Pipeline 模式 ​

流水线处理 ​

go
// ===== Pipeline:流水线作业 =====
func pipelineExample() {
    // Stage 1:生成数字
    nums := generate(1, 2, 3, 4, 5, 6, 7, 8)

    // Stage 2:平方
    squared := square(nums)

    // Stage 3:过滤(只保留 > 10 的)
    filtered := filterGT(squared, 10)

    // Stage 4:打印
    for v := range filtered {
        fmt.Println(v)
    }
}

func generate(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)
    }()
    return out
}

func square(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            out <- n * n
        }
        close(out)
    }()
    return out
}

func filterGT(in <-chan int, threshold int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            if n > threshold {
                out <- n
            }
        }
        close(out)
    }()
    return out
}

并发 Pipeline ​

go
// ===== 并发 Pipeline:每个 Stage 有多个 Worker =====
func concurrentPipeline() {
    jobs := generate(1, 2, 3, 4, 5, 6, 7, 8)

    // Stage 1:平方(3 个 Worker)
    stage1 := make(chan int, 10)
    for i := 0; i < 3; i++ {
        go func(id int) {
            for n := range jobs {
                stage1 <- n * n
            }
        }(i)
    }

    // Stage 2:过滤(2 个 Worker)
    stage2 := make(chan int, 10)
    for i := 0; i < 2; i++ {
        go func(id int) {
            for n := range stage1 {
                if n%2 == 0 {
                    stage2 <- n
                }
            }
        }(i)
    }

    // 收集结果
    go func() {
        for n := range stage2 {
            fmt.Println(n)
        }
    }()
}

常见并发模式 ​

Once 懒加载单例 ​

go
// ===== 单例模式 =====
import "sync"

type Database struct {
    conn string
}

var (
    db     *Database
    dbOnce sync.Once  // 只执行一次

    // 懒加载(推荐方式)
    dbOnceFunc = func() *Database {
        // 初始化数据库连接
        conn, _ := connectDB()
        return &Database{conn: conn}
    }
)

func GetDB() *Database {
    dbOnce.Do(func() {
        db = dbOnceFunc()
    })
    return db
}

// ===== 连接池(原子操作)=====
import "sync/atomic"

type Counter struct {
    value int64
}

func (c *Counter) Inc() {
    atomic.AddInt64(&c.value, 1)
}

func (c *Counter) Value() int64 {
    return atomic.LoadInt64(&c.value)
}

并发安全 Map ​

go
// ===== sync.Map:读多写少的并发安全 Map =====
import "sync"

var cache sync.Map

// 存储
cache.Store("user:1", User{Name: "Alice"})
cache.Store("user:2", User{Name: "Bob"})

// 读取
if v, ok := cache.Load("user:1"); ok {
    user := v.(User)
    fmt.Println(user.Name)
}

// 读取或计算(不存在时计算并存储)
user, _ := cache.LoadOrStore("user:3", User{Name: "Charlie"})
_ = user

// 遍历
cache.Range(func(key, value any) bool {
    fmt.Printf("%s: %v\n", key, value)
    return true  // return false 停止遍历
})

// 删除
cache.Delete("user:1")

Timeout 通道模式 ​

go
// ===== 超时等待 =====
func withTimeout() {
    result := make(chan string, 1)

    go func() {
        // 模拟耗时操作
        time.Sleep(3 * time.Second)
        result <- "完成"
    }()

    select {
    case res := <-result:
        fmt.Println("成功:", res)
    case <-time.After(1 * time.Second):
        fmt.Println("超时!")
    }
}

// ===== 并发等待 + 收集结果 =====
import "sync"

func collectWithTimeout(ctx context.Context, tasks []func() error) []error {
    results := make([]error, len(tasks))
    var wg sync.WaitGroup

    for i, task := range tasks {
        i, task := i, task
        wg.Add(1)
        go func() {
            defer wg.Done()

            // 单个任务超时
            taskCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
            defer cancel()

            done := make(chan error, 1)
            go func() {
                done <- task()
            }()

            select {
            case results[i] = <-done:
                // 完成
            case <-taskCtx.Done():
                results[i] = fmt.Errorf("任务 %d 超时", i)
            }
        }()
    }

    wg.Wait()
    return results
}

生产级并发架构 ​

优雅关闭 ​

go
// ===== 优雅关闭:等待正在处理的任务完成 =====
type Server struct {
    shutdown chan struct{}
    jobs     chan Job
    wg       sync.WaitGroup
    running  atomic.Int32
}

func NewServer(queueSize int) *Server {
    return &Server{
        shutdown: make(chan struct{}),
        jobs:     make(chan Job, queueSize),
    }
}

func (s *Server) Start(numWorkers int) {
    for i := 0; i < numWorkers; i++ {
        s.wg.Add(1)
        go s.worker(i)
    }
}

func (s *Server) worker(id int) {
    defer s.wg.Done()
    for {
        select {
        case job := <-s.jobs:
            s.running.Add(1)
            job.Do()
            s.running.Add(-1)
        case <-s.shutdown:
            // 收到关闭信号,等待当前任务完成
            for len(s.jobs) > 0 || s.running.Load() > 0 {
                time.Sleep(100 * time.Millisecond)
            }
            return
        }
    }
}

func (s *Server) Submit(job Job) bool {
    select {
    case s.jobs <- job:
        return true
    case <-s.shutdown:
        return false
    default:
        return false  // 队列满了
    }
}

func (s *Server) Shutdown() {
    close(s.shutdown)  // 通知所有 Worker 进入关闭流程
    s.wg.Wait()       // 等待所有 Worker 完成
}

// HTTP 服务中使用
/*
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    job := &HTTPJob{w, r}
    if !s.Submit(job) {
        http.Error(w, "服务繁忙", 503)
    }
}

func main() {
    srv := NewServer(100)
    srv.Start(10)

    // 监听 SIGTERM / SIGINT
    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)

    <-sigCh
    srv.Shutdown()
}
*/

Go vs Python 并发对比 ​

并发原语对比:
─────────────────────────────────────────────────────
Go                      Python                    说明
─────────────────────────────────────────────────────
goroutine + channel     asyncio + await           协程并发
sync.WaitGroup         asyncio.gather            等待多个任务
context.Context         asyncio.timeout / None     取消/超时
errgroup               concurrent.futures         错误收集
semaphore              asyncio.Semaphore          限流
sync.Mutex             threading.Lock             互斥锁
sync.RWMutex           threading.RLock            读写锁
sync.Map               concurrent.futures.ThreadSafeDict  并发 Map
sync.Once              threading.Once             单次执行
atomic (AddInt64等)    threading.atomic / lock   原子操作
─────────────────────────────────────────────────────

场景对比:
─────────────────────────────────────────────────────
场景           Go 方案                Python 方案
─────────────────────────────────────────────────────
高并发 HTTP   goroutine + channel   asyncio + aiohttp
CPU 密集      多进程                multiprocessing
文件 I/O      goroutine             asyncio / threading
数据库连接池  goroutine + sync.Pool  SQLAlchemy 池
Worker Pool   channel + goroutine   concurrent.futures
优雅关闭      context + WaitGroup   asyncio.CancelledError
限流          semaphore             Semaphore
并发爬虫      errgroup + semaphore  asyncio + Semaphore
pipeline      channel pipeline      asyncio 队列
─────────────────────────────────────────────────────

核心理念差异:
Go:通信顺序进程(CSP)→ 通过 channel 通信共享内存
     channel 是 first-class,可以传递、缓存、选择
     goroutine 是语言内置,轻量到可以随意创建

Python:共享内存 + 锁 → asyncio 是协作式多任务
     async/await 是语法糖,本质是事件循环调度协程
     受 GIL 限制(但 I/O 时自动释放)
     threading 是 OS 线程,受 GIL 限制

结论:Go 的并发是编译器和运行时层面的优化,Python 的 asyncio 是解释器层面的优化。
Go 适合超高并发(1000+ 并发),Python asyncio 适合 IO 密集 + 快速开发。
─────────────────────────────────────────────────────

常见陷阱与最佳实践 ​

陷阱 1:goroutine 泄漏 ​

go
// ❌ 陷阱:channel 永不关闭,goroutine 泄漏
func leak() {
    ch := make(chan int)
    go func() {
        for i := range ch {  // 如果没人发送,永久阻塞
            fmt.Println(i)
        }
    }()
    // ch 永远不会被关闭,goroutine 永远存在
}

// ✅ 正确:使用 context 取消
func noLeak(ctx context.Context) {
    ch := make(chan int)
    go func() {
        defer close(ch)  // 确保退出时关闭
        for {
            select {
            case <-ctx.Done():
                return  // 被取消时退出
            case v, ok := <-ch:
                if !ok {
                    return
                }
                fmt.Println(v)
            }
        }
    }()
}

陷阱 2:共享变量竞态 ​

go
// ❌ 陷阱:map 不是线程安全的
var counter = make(map[string]int)

func bad() {
    for i := 0; i < 1000; i++ {
        go func() {
            counter["key"]++  // 并发写入 map,崩溃
        }()
    }
}

// ✅ 正确:用 sync.Mutex 或 sync.Map
var mu sync.Mutex
var safeCounter = make(map[string]int)

func good() {
    for i := 0; i < 1000; i++ {
        go func() {
            mu.Lock()
            safeCounter["key"]++
            mu.Unlock()
        }()
    }
}

// ✅ 或者用 atomic(适用于计数器)
var atomicCounter atomic.Int64

func atomicWay() {
    for i := 0; i < 1000; i++ {
        go func() {
            atomicCounter.Add(1)
        }()
    }
}

陷阱 3:select 中的 nil channel ​

go
// ❌ 陷阱:往 nil channel 发送/接收会永久阻塞
var ch1 chan int
var ch2 chan int = make(chan int)

select {
case <-ch1:  // ch1 是 nil,永久阻塞
    fmt.Println("ch1")
case <-ch2:
    fmt.Println("ch2")
}

// ✅ 正确:动态启用/禁用 case
func dynamicSelect(stopCh chan struct{}) {
    ch := make(chan int)
    isActive := true

    for {
        select {
        case <-stopCh:
            return
        case v := <-ch:
            fmt.Println(v)
        default:
            if !isActive {
                // channel 被禁用
            }
        }
    }
}

总结 ​

Context 速查:
─────────────────────────────────────────────────────
context.Background()                    根 Context
context.WithCancel(parent)             主动取消
context.WithTimeout(parent, d)         超时取消
context.WithDeadline(parent, time)     定时取消
context.WithValue(parent, key, val)    值传递
ctx.Done()                             取消信号 channel
ctx.Err()                              取消原因
ctx.Value(key)                         获取值
─────────────────────────────────────────────────────
errgroup 速查:
─────────────────────────────────────────────────────
g := new(errgroup.Group)               创建 Group
g.Go(func() error { ... })             启动任务
err := g.Wait()                       等待所有完成,返回第一个错误
g, ctx := errgroup.WithContext(ctx)    带 Context 的 Group
─────────────────────────────────────────────────────
Channel 速查:
─────────────────────────────────────────────────────
make(chan T)              无缓冲(同步)
make(chan T, n)          有缓冲(异步)
ch <- v                  发送(阻塞)
v := <-ch                接收(阻塞)
close(ch)                关闭
for v := range ch        遍历(自动在 close 时退出)
select { case ... }      多路复用
default                  非阻塞
nil channel              永久阻塞(用于禁用 case)
─────────────────────────────────────────────────────
sync 包速查:
─────────────────────────────────────────────────────
sync.Mutex              互斥锁
sync.RWMutex           读写锁(读多写少)
sync.WaitGroup         等待组
sync.Once              只执行一次
sync.Map               并发安全 Map(读多写少)
sync.Cond              条件变量
sync.Pool              对象池(复用对象)
sync/atomic            原子操作
golang.org/x/sync/semaphore  信号量(限流)
golang.org/x/sync/errgroup   错误收集 + 等待
─────────────────────────────────────────────────────
最佳实践:
─────────────────────────────────────────────────────
✅ Context 要传递,不要存成员变量
✅ goroutine 要配 select + ctx.Done()
✅ channel 用 defer close 确保持续关闭
✅ defer mu.Unlock() 不要漏
✅ 共享 map 用 sync.Map 或加锁
✅ Worker Pool 用 channel 作为任务队列
✅ errgroup.Go() 的回调不要捕获循环变量(要显式赋值)
✅ 超时用 context.WithTimeout,不要用 time.Sleep
✅ 生产环境:Context 超时 + errgroup 取消 + semaphore 限流
✅ 优雅关闭:close(shutdown) + WaitGroup.Wait()
✅ 性能关键:用 atomic 而非锁(计数器等简单场景)
✅ Pipeline:每个 stage 用独立的 channel 隔离
─────────────────────────────────────────────────────

Go 的并发哲学是"不要通过共享内存通信,而要通过通信来共享内存"。goroutine + channel + context + errgroup 构成了 Go 并发编程的四大支柱。掌握这些,你就能写出既优雅又高性能的并发服务 🦐

本文由小虾子 🦐 撰写

Last updated: