注意:下述所有图片中 - 号是BUG代码,+号是修复BUG的代码
1.死锁
goroutine1 ch发送者被阻塞住,无法释放锁
goroutine2 loop获取锁失败
2.goroutine永久泄露
context.WithCancel内部启动goroutine,在ctx被覆盖后goroutin永久泄露
3.waitgroup使用不当,永久阻塞
使用WaitGroup一定要遵守的原则就是,等所有的Add方法调用之后再调用Wait,否则就可能导致panic或者不期望的结果
4.闭包捕获本地变量
5.goroutine启动前要保证waitgroup.Add完成
6.多次关闭同一个channel
关闭已经关闭的channel会导致panic,因此在并发编程中要处理好channel的关闭
7.map 并发读写
这个bug无法被recover
线上做好supervisor和stderr重定向到文件,不然两眼一抹黑
共享资源竞争的问题,非常复杂,并且难以察觉,好在 Go 为我们提供了一个工具帮助我们检查,这个就是go build -race
命令。在项目目录下执行这个命令,生成一个可以执行文件,然后再运行这个可执行文件,就可以看到打印出的检测信息。
并发场景下考虑使用:sync.map
8.不控制gouroutine数量
这个例子实现了 math.MaxInt32
个协程的并发,约 2^31 = 2 亿个
运行会直接导致:panic: too many concurrent operations on a single file or socket (max 1048575)
不同的应用程序,消耗的资源是不一样的。比较推荐的方式的是:应用程序来主动限制并发的协程数量。
- 利用缓冲信道实现[2]
make(chan struct{}, 3)
创建缓冲区大小为 3 的 channel,在没有被接收的情况下,至多发送 3 个消息则被阻塞。- 开启协程前,调用
ch <- struct{}{}
,若缓存区满,则阻塞。- 协程任务结束,调用
<-ch
释放缓冲区。sync.WaitGroup
并不是必须的,例如 http 服务,每个请求天然是并发的,此时使用 channel 控制并发处理的任务数量,就不需要sync.WaitGroup
。
- 利用第三方协程池库实现,比较受欢迎的有[2]