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 泄漏。