Go-10-回调、闭包与并发入门
本节目标:理解 Go 的并发模型(CSP),掌握 goroutine + channel + select,理解”用通信共享内存而不是用内存通信”。
1. goroutine:轻量级线程
1 | func say(s string) { |
关键事实:
- goroutine 由 Go runtime 管理,不是 OS 线程
- 初始栈 2KB(可动态增长),可以轻松开上百万个
go关键字启动一个 goroutine,立即返回,不等待
主 goroutine 退出时,所有子 goroutine 立即终止(程序退出):
1 | func main() { |
同步等待:
1 | var wg sync.WaitGroup |
2. channel:goroutine 之间的通信
channel 是类型化的管道,用 make 创建:
1 | ch := make(chan int) // 无缓冲 channel |
2.1 无缓冲 channel:同步
无缓冲 = 发送方阻塞直到接收方准备好:
1 | ch := make(chan int) |
2.2 缓冲 channel:异步
缓冲 channel 发送方在缓冲区未满时不会阻塞:
1 | ch := make(chan int, 2) |
2.3 关闭 channel
发送方 close,接收方检测关闭:
1 | ch := make(chan int) |
关闭原则:
- 关闭后不能再发送(会 panic)
- 接收方不会因关闭而阻塞,可以继续接收剩余值
- 不要从接收方关闭 channel(容易 panic)
- 不要关闭已关闭的 channel
3. select:多路复用
1 | ch1 := make(chan string) |
select 的关键特性:
- 多个 case 同时就绪时随机选一个(避免饥饿)
default让 select 变成”非阻塞”time.After(d)在 d 时间后发送一个值,常用于超时
4. 实战:并发安全的工作池
1 | type Job struct { |
5. 实战:超时控制
1 | func fetchWithTimeout(url string, timeout time.Duration) (string, error) { |
6. 实战:done channel 模式(取消)
1 | func doWork(done <-chan struct{}) <-chan int { |
7. CSP 模型:通信顺序进程
Go 的并发哲学:
“不要通过共享内存来通信;而要通过通信来共享内存。”
传统方式(共享内存 + 锁):
1 | var counter int |
Go 风格(通过 channel 通信):
1 | counter := make(chan int) |
两种方式都有效,但 channel 风格:
- 数据所有权明确(谁发送谁拥有)
- 避免竞态条件
- 更容易组合(多个 channel 通过 select 复用)
8. 并发陷阱
8.1 死锁
1 | ch := make(chan int) |
8.2 竞态条件(race condition)
1 | var counter int |
检测竞态:go test -race 或 go run -race main.go
8.3 goroutine 泄漏
1 | func leak() { |
避免:用 context、超时、done channel 通知退出。
9. 并发 Map(sync.Map)
普通 map 多 goroutine 写会 panic,用 sync.Map 或加锁:
1 | var m sync.Map |
或者用 map + sync.RWMutex:
1 | type SafeMap struct { |
10. WaitGroup 详解
1 | var wg sync.WaitGroup |
注意:
wg.Add必须在go语句之前调用- 计数不能为负(
Done比Add多会 panic)
小结
| 概念 | 用法 |
|---|---|
go func() |
启动 goroutine |
ch := make(chan T) |
无缓冲 channel(同步) |
ch := make(chan T, n) |
缓冲 channel(异步) |
ch <- v / <-ch |
发送/接收 |
close(ch) |
关闭 channel |
for v := range ch |
接收直到关闭 |
select |
多 channel 复用 |
sync.WaitGroup |
等多个 goroutine |
sync.Mutex / RWMutex |
互斥锁 |
sync.Map |
并发安全的 map |
time.After / time.Tick |
超时/定时 |
最佳实践:
- 能用 channel 就别用共享内存(但也别走极端)
- 永远关心 goroutine 退出(避免泄漏)
- 用
-race跑测试 - buffered channel 的缓冲大小要算清楚
- 不要在不知道对方会如何关闭时 close channel
下一节进入并发深入:sync 包、context、连接池。
- 本文作者: CoderSong
- 本文链接: https://jack-song-gif.github.io/2026/08/23/Go-10-回调与并发入门/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!