Go 并发控制:常见用法与踩坑指南 Go 的并发模型基于 CSP(Communicating Sequential Processes)理念,核心口号是「不要通过共享内存来通信,而要通过通信来共享内存」。但实际项目中, sync 包和共享内存的用法同样常见。这份文档把常见工具和典型坑点放在一起,方便对照查阅。 一、基础并发原语 1. goroutine go func () { doSomething() }() 启动成本很低(初始栈约 2KB,按需增长),但 不代表可以无限制启动 。 go 关键字只是把函数丢给调度器,不保证执行顺序,也不保证主协程会等它结束。 常见坑:主协程提前退出 func main () { go fmt.Println( "hello" ) // main 函数直接结束,goroutine 可能还没来得及执行 } 主协程退出时,其余 goroutine 会被直接杀死,不会等待。必须用 WaitGroup 、 channel 或其他同步手段显式等待。 2. channel ch := make ( chan int ) // 无缓冲,发送阻塞直到有人接收 ch := make ( chan int , 10 ) // 有缓冲,容量满了才阻塞 无缓冲 channel 是同步点,发送方和接收方必须「握手」。 有缓冲 channel 只在缓冲区满/空时才阻塞,容易掩盖设计问题(比如用大缓冲区掩盖生产消费速度不匹配)。 常见坑一:向已关闭的 channel 发送数据 close (ch) ch <- 1 // panic: send on closed channel 常见坑二:重复关闭 channel close (ch) close (ch) // panic: close of closed channel 原则: 由发送方关闭 channel,且只关闭一次 ;接收方永远不要关闭。如果有多个发送方,用 sync.Once 或专门的协调协程来关闭。 常见坑三:nil channel 导致永久阻塞 对 nil channel 的收发操作会永远阻塞(这在 select 里其实是一个有用的技巧,用来临时...
Go 语言里的 goroutine 泄露(goroutine leak) ,它通常发生在: goroutine 已经没有实际工作可做、也无法退出,但仍然被 Go runtime 保留。 常见情况有这几种: 1. 永久阻塞在 channel func foo () { ch := make ( chan int ) go func () { x := <-ch // 一直等,但没人发送 fmt.Println(x) }() } 如果后续没人向 ch 发送数据,这个 goroutine 就会一直阻塞。 2. channel 没有消费者 ch := make ( chan int ) go func () { ch <- 1 // 一直卡在这里 }() 如果没有其他 goroutine 接收: <-ch 这个 goroutine 就无法继续执行,也无法退出。 3. 忘记关闭 channel / 退出条件 典型例子: func worker (ch <- chan int ) { go func () { for { x := <-ch fmt.Println(x) } }() } 如果 ch 永远不关闭,且没有其他退出机制,这个 goroutine 可能永久运行。 更常见的写法是: for x := range ch { fmt.Println(x) } 然后由生产者: close (ch) 让消费者自然退出。 4. select 一直等不到退出信号 go func () { for { select { case x := <-ch: fmt.Println(x) } } }() 如果 ch 永远没有数据,这个 goroutine 会一直活着。 通常应该加一个退出机制: go func () { for { select { ...