提示:本网站现在为维护调试状态

Go 言語並行:goroutine + channel + sync.WaitGroup

Choyeon
Choyeon
2026年9月8日 1 分で読了 588 閲覧
401 字 2,421 文字 更新日 2026年11月8日
Go 言語並行:goroutine + channel + sync.WaitGroup
目次

Go Concurrency: Goroutines, Channels & sync.WaitGroup

Go's defining feature is native concurrency: "don't communicate by sharing memory; share memory by communicating". CSP pattern delivers highly readable, safe concurrent code.

Goroutines and Worker Pool Pattern

A goroutine starts with 2KB stack, so hundreds of thousands are practical. Bound concurrency with buffered channel for fixed worker pool; avoid runaway creation stressing scheduler. Use sync.WaitGroup to await graceful shutdown.

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 Cancellation and Pitfalls

context.WithTimeout/WithCancel propagates cancellation through all nested goroutines - key defense against leaks. Common traps: for-range on closed channels exit cleanly, select with default causes busy loops, unbuffered channel send blocks and hangs goroutines.

Feature Go goroutine+channel Java Thread+Queue Rust async+tokio
Min cost ~2KB stack ~1MB stack Struct-level
Spawn 100k time <100ms Seconds/OOM ~200ms
Sync primitives channel/mutex/Cond BlockingQueue/locks tokio::sync::*
Cancel mechanism Unified Context Future.cancel/interrupt Drop/select!
Pitfalls Goroutine leaks/blocked Spurious wakeups/deadlock Pin! lifetimes

Best Practices

Senders close channels, not receivers. Prefer bounded buffered channels to avoid unbounded memory growth. Cover all exit paths with defer for cleanup; exceptions otherwise silently leak goroutines.

X微博Telegram

筆者について

投票

你最希望 Rosetta 优先加入哪项功能?

社区投票(演示数据,可后台删除)

コメント (6)

0 / 3000
摸鱼达人小王
摸鱼达人小王2026年9月21日

好文!但是我觉得第三部分那个消息队列选型对比表有点偏向 Kafka 了,RocketMQ 在事务消息这块其实优势挺明显的。

机器学习萌新
机器学习萌新2026年9月21日

受益匪浅,感谢大佬分享!

运维老司机
运维老司机ops-notes.cn2026年9月21日

哈哈,楼主的踩坑经历和我一模一样!我当时就是没注意到这个配置,排查了整整两天,看到这篇文章泪目了。

数据库小管家
数据库小管家dbtips.pro2026年9月20日

楼主,我按照你的步骤操作后启动报错:Error creating bean with name 'dataSource',版本是 Spring Boot 3.2,请问有遇到过吗?

架构师小李
架构师小李archinsights.blog2026年9月20日

代码示例里那个 SQL 查询是不是应该加个 LIMIT?不然数据量大的时候全表扫描会很慢哦。

全栈打工人
全栈打工人2026年9月20日

写得太好了,终于搞懂了分布式事务的几种方案区别,收藏了!

関連記事