Go 并发核心在于理解 channel 阻塞语义、select 非抢占调度及 sync.Mutex 适用场景;需用 sync.WaitGroup 等同步机制避免主 goroutine 提前退出,防止循环变量复用导致数据错误,禁用 time.Sleep 做同步,避免 channel 读写不配对引发死锁,合理选择缓冲/无缓冲 channel,关闭 channel 前确保写端完成,select 随机选就绪分支且仅支持纯通信操作。
Go 并发不是靠“多开 goroutine”就能写对的,核心在于理解 channel 的阻塞语义、select 的非抢占式调度,以及何时该用 sync.Mutex 而非靠 channel 串行化。
新手常以为 go f() 启动后函数会自然执行完,但主 goroutine 退出时整个程序立即终止,其他 goroutine 来不及执行。
sync.WaitGroup 显式等待:调用 wg.Add(1) 在启动前,wg.Done() 在 goroutine 结束时,主协程调用 wg.Wait()
for _, v := range items { go func() { println(v) }() }),会导致所有 goroutine 看到同一个 v 的最终值;应传参:go func(val string) { println(val) }(v)
time.Sleep 不是同步手段,仅用于调试;生产代码中它掩盖了竞态,且无法保证等待足够久向无缓冲 channel 发送数据会阻塞,直到有 goroutine 准备接收;若发送方和接收方没对齐(比如只发不收、或只收不发),fatal error: all goroutines are asleep - deadlock! 立刻出现。
make(chan int))适合严格的一对一同步;有缓冲 channel(make(chan int, 10))可缓解生产者/消费者速率差异,但缓冲区满后仍会阻塞发送select 配合 default 可实现非阻塞尝试读/写,避免卡住select 会在多个 channel 操作中**随机选择一个就绪的分支**执行,没有优先级,也没有“条件判断”逻辑——它只看 channel 是否可读/可写。
ch 或 ),不能带函数调用或赋值表达式
time.After(d) 构造的 channel;想避免阻塞,加 default: 分支select{} 会永久阻塞,等价于 for {},常用于让主 goroutine 等待信号select {
case msg := <-ch:
fmt.Println("received", msg)
case <-time.After(2 * time.Second):
fmt.Println("timeout")
default:
fmt.Println("no message ready")
}channel 用于 goroutine 间**传递数据与控制流**;sync.Mutex 用于保护**共享内存的临界区**。混用或误用会导致隐蔽 bug 或性能瓶颈。
mu.Lock()/Unlock() 包裹,不能指望 channel 转发来“串行化”
sync.RWMutex 适合读多写少场景,允许多个 reader 并发,但 writer 仍独占真正难的不是写出能跑的并发代码,而是判断某个状态是否被多个 goroutine 共享、哪些操作必须原子、channel 边界是否清晰——这些没法靠抄例子解决,得在 debug 竞态(go run -race)和重读 runtime.gopark 行为中慢慢建立直觉。