主题
06 · 并发:goroutine / channel / context
目标:写「能结束、能超时、不泄漏」的小并发,而不是抄花式 channel 魔术。
官方:Tour Concurrency · Effective Go · Concurrency ·context·sync
1. 心智模型(对照 Node)
| Node | Go |
|---|---|
| 单线程事件循环 + async | 多 goroutine,由运行时调度 |
| Promise / await | 常用 channel 通信或 WaitGroup 同步 |
AbortController | context.Context 取消与超时 |
| 共享内存靠小心 | 口号:不要通过共享内存来通信;通过通信来共享内存(见 Effective Go / Proverbs) |
入门只掌握三件套:goroutine、channel、context;Mutex 知道即可,优先少共享。
2. Goroutine + WaitGroup
go
var wg sync.WaitGroup
for _, u := range urls {
wg.Add(1)
go func(url string) {
defer wg.Done()
// fetch(url) ...
}(u) // 注意:循环变量要传参(旧版本易踩坑;新版本有变化,仍建议显式传)
}
wg.Wait()1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
AI 高发坑:Add/Done 不配对;循环变量捕获错误;主函数提前 return 导致进程退出。
3. Channel 最小用法
go
ch := make(chan string, 3) // 带缓冲
ch <- "a"
v := <-ch
close(ch) // 只由发送方 close
for v := range ch {
_ = v
}1
2
3
4
5
6
7
2
3
4
5
6
7
| 规则 | 说明 |
|---|---|
| 发送方 close | 接收方不要 close |
| 向已 close 的 channel 发送 | panic |
| 无缓冲 | 同步会合;有缓冲可解耦 |
select | 多路等待;常配 ctx.Done() |
Tour 里的 select、等价二叉树等练习值得亲手做。
4. context:超时与取消(生产必备)
go
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
select {
case res := <-doWork(ctx):
fmt.Println(res)
case <-ctx.Done():
fmt.Println("timeout:", ctx.Err())
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
惯例:
- 上下文作函数第一个参数:
func Fetch(ctx context.Context, url string) - 向下传递同一
ctx;超时在根处设置 - 文档:https://pkg.go.dev/context
HTTP 客户端、数据库驱动都认 context——第 07、10 篇会用到。
5. 推荐小练习:「并发抓 3 个 URL」
要求:
- 三个 goroutine 请求(可用
net/http,或先time.Sleep模拟) - 总超时 2s(
WithTimeout) - 收集成功结果;超时/失败要能从
err看出 go test或至少go run可结束(无泄漏挂死)
这是暑假计划里点名的 demo。
6. 先别碰的(入门周)
- 复杂的 channel 编排图、无缓冲死锁炫技
- 一上来
errgroup全家桶(可随后学:golang.org/x/sync/errgroup) - 在没理解 race 前乱加
Mutex
怀疑数据竞争时:
bash
go test -race ./...1
7. AI 审查提示词
text
审查这段 Go 并发代码:
1) 是否所有 goroutine 都能退出?
2) WaitGroup / channel close 是否正确?
3) 是否使用 context 做超时?
4) 有无经典循环变量陷阱?
请给出最小修复,不要引入框架。1
2
3
4
5
6
2
3
4
5
6
8. 验收清单
- [ ] 能写 WaitGroup 版并行任务并正常结束
- [ ] 能写带
select+ctx.Done()的超时 - [ ] 知道谁该
close(ch) - [ ] 跑过一次
-race(有测试时)
