经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Go语言 » 查看文章
Go语言死锁与goroutine泄露问题的解决
来源:jb51  时间:2021/7/26 12:07:01  对本文有异议

什么时候会导致死锁

在计算机组成原理里说过 死锁有三个必要条件他们分别是 循环等待、资源共享、非抢占式,在并发中出现通道死锁只有两种情况:

  • 数据要发送,但是没有人接收
  • 数据要接收,但是没有人发送

发送单个值时的死锁

牢记这两点问题就很清晰了,复习下之前的例子,会死锁

  1. a := make(chan int)
  2. a <- 1 //将数据写入channel
  3. z := <-a //从channel中读取数据
  • 有且只有一个协程时,无缓冲的通道
  • 先发送会阻塞在发送,先接收会阻塞在接收处。
  • 发送操作在接收者准备好之前是阻塞的,接收操作在发送之前是阻塞的,

解决办法就是改为缓冲通道,或者使用协程配对

解决方法一,协程配对,先发送还是先接收无所谓只要配对就好

  1. chanInt := make(chan int)
  2. go func() {
  3. chanInt <- 1
  4. }()
  5.  
  6. res := <-chanInt

解决方法二,缓冲通道

  1. chanInt := make(chan int,1)
  2. chanInt <- 2
  3. res := <-chanInt
  • 缓冲通道内部的消息数量用len()函数可以测试出来
  • 缓冲通道的容量可以用cap()测试出来
  • 在满足cap>len时候,因为没有满,发送不会阻塞
  • 在len>0时,因为不为空,所以接收不会阻塞

使用缓冲通道可以让生产者和消费者减少阻塞的可能性,对异步操作更友好,不用等待对方准备,但是容量不应设置过大,不然会占用较多内存。

多个值发送的死锁

配对可以让死锁消失,但发送多个值的时候又无法配对了,又会死锁

  1. func multipleDeathLock() {
  2. chanInt := make(chan int)
  3. defer close(chanInt)
  4. go func() {
  5. res := <-chanInt
  6. fmt.Println(res)
  7. }()
  8. chanInt <- 1
  9. chanInt <- 1
  10. }

不出所料死锁了

  1. fatal error: all goroutines are asleep - deadlock!
  2.  
  3. goroutine 1 [chan send]:
  4. main.multipleDeathLock()
  5.  

在工作中只有通知信号是一对一的情况,通知一次以后就不再使用了,其他这种要求多次读写配对的情况根本不会存在。

解决多值发送死锁

更常见的是用循环来不断接收值,接受一个处理一个,如下:

  1. func multipleLoop() {
  2. chanInt := make(chan int)
  3. defer close(chanInt)
  4. go func() {
  5. for {
  6. //不使用ok会goroutine泄漏
  7. //res := <-chanInt
  8. res,ok := <-chanInt
  9. if !ok {
  10. break
  11. }
  12. fmt.Println(res)
  13. }
  14. }()
  15. chanInt <- 1
  16. chanInt <- 1
  17. }

输出:

1
1

  • 给通道的接收加上二值,ok 代表通道是否正常,如果是关闭则为false值
  • 可以删掉那段逻辑试试,会输出1 2 0 0 0这样的数列,因为关闭是需要时间的,而循环接收关闭的通道拿到的是0
  • 关于goroutine泄漏稍后会讲到

应该先发送还是先接收

假如我们调换一下位置,把接收放外面,写入放里面会发生什么

  1. func multipleDeathLock2() {
  2. chanInt := make(chan int)
  3. defer close(chanInt)
  4. go func() {
  5. chanInt <- 1
  6. chanInt <- 2
  7. }()
  8. for {
  9. res, ok := <-chanInt
  10. if !ok {
  11. break
  12. }
  13. fmt.Println(res)
  14. }
  15. }

输出死锁
1
2
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.multipleDeathLock2()

  • 出现上面的结果是因为for循环一直在获取通道中的值,但是在读取完1 2后,通道中没有新的值传入,这样接收者就阻塞了。
  • 为什么先接收再发送可以,因为发送提前结束后会触发函数的defer自动关闭通道
  • 所以我们应该总是先接收后发送,并由发送端来关闭

goroutine 泄漏

goroutine 终止的场景有三个:

  • 当一个 goroutine 完成了它的工作
  • 由于发生了没有处理的错误
  • 有其他的协程告诉它终止

当三个条件都没有满足,goroutine 就会一直运行下去

  1. func goroutineLeak() {
  2. chanInt := make(chan int)
  3. defer close(chanInt)
  4. go func() {
  5. for {
  6. res := <-chanInt
  7. //res,ok := <-chanInt
  8. //if !ok {
  9. // break
  10. //}
  11. fmt.Println(res)
  12. }
  13. }()
  14. chanInt <- 1
  15. chanInt <- 1
  16. }

上面的goroutineLeak()函数结束后触发defer close(chanInt)关闭了通道
但是匿名函数中goroutine并没有关闭,而是一直在循环取值,并且取到是的关闭后的通道值(这里是int的默认值 0)
goroutine会永远运行下去,如果以后再次使用又会出现新的泄漏!导致内存、cpu占用越来越多

输出,如果程序不停止就会一直输出0
1
1
0
0
0
...

假如不关闭且外部没有写入值,那接收处就会永远阻塞在那里,连输出都不会有

  1. func goroutineLeakNoClosed() {
  2. chanInt := make(chan int)
  3. go func() {
  4. for {
  5. res := <-chanInt
  6. fmt.Println(res)
  7. }
  8. }()
  9. }
  • 无任何输出的阻塞
  • 换成写入也是一样的
  • 如果是有缓冲的通道,换成已满的通道写没有读;或者换成向空的通道读没有写也是同样的情况
  • 除了阻塞,goroutine进入死循环也是泄露的原因

如何发现泄露

  • 使用 golang 自带的pprof监控工具,可以发现内存上涨情况,这个后续会讲
  • 还可以监控进程的内存使用情况,比如prometheus提供的process-exporter
  • 如果你有内存泄露/goroutine 泄露代码扫描的工具,欢迎留言,感恩!

小结

今天我们学习了一些细节,但是相当重要的知识点,也是未来面试高频问题哦!

  • 如果是信号通知,应该保证一一对应,不然会死锁
  • 除了信号通知外,通常我们使用循环处理通道,在工作中不断的处理数据
  • 应该总是先接收后发送,并由发送端来关闭,不然容易死锁或者泄露
  • 在接收处,应该对通道是否关闭做好判断,已关闭应该退出接收,不然会泄露
  • 小心 goroutine 泄漏,应该在通道关闭的时候及时检查通道并退出
  • 除了阻塞,goroutine进入死循环也是泄露的原因

本节源码地址  

到此这篇关于Go语言死锁与goroutine泄露问题谈论的文章就介绍到这了,更多相关Go语言死锁与goroutine泄露内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号