1、Golang中死锁的触发条件

1.1 书上关于死锁的四个必要条件的讲解

发生死锁时,线程永远不能完成,系统资源被阻碍使用,以致于阻止了其他作业开始执行。在讨论处理死锁问题的各种方法之前,我们首先深入讨论一下死锁特点。

必要条件:

如果在一个系统中以下四个条件同时成立,那么就能引起死锁:

  1. 互斥:至少有一个资源必须处于非共享模式,即一次只有一个线程可使用。如果另一线程申请该资源,那么申请线程应等到该资源释放为止。
  2. 占有并等待:—个线程应占有至少一个资源,并等待另一个资源,而该资源为其他线程所占有。
  3. 非抢占:资源不能被抢占,即资源只能被线程在完成任务后自愿释放。
  4. 循环等待:有一组等待线程 {P0,P1,…,Pn},P0 等待的资源为 P1 占有,P1 等待的资源为 P2 占有,……,Pn-1 等待的资源为 Pn 占有,Pn 等待的资源为 P0 占有。

我们强调所有四个条件必须同时成立才会出现死锁。循环等待条件意味着占有并等待条件,这样四个条件并不完全独立。

图示例:

线程1、线程2都尝试获取对方未释放的资源,从而会一直阻塞,导致死锁发生。

1.2 Golang 死锁的触发条件

看完了书上关于死锁的介绍,感觉挺清晰的,但是实际上到了使用或者看代码时,自己去判断是否会发生死锁却是模模糊糊的,难以准确判断出来。所以特意去网上找了些资料学习,特此记录。

golang中死锁的触发条件:

  1. 死锁是当 Goroutine 被阻塞而无法解除阻塞时产生的一种状态。注意:for 死循环不能算在这里,虽然空for循环是实现了阻塞的效果,但是实际上goroutine是处于运行状态的。

1.3 golang 中阻塞的场景

1.3.1 sync.Mutex、sync.RWMutex

golang中的锁是不可重入锁,对已经上了锁的写锁,再次申请锁是会报死锁。上了读锁的锁,再次申请写锁会报死锁,而申请读锁不会报错。

写写冲突,读写冲突,读读不冲突。

  1. func main() {
  2. var lock sync.Mutex
  3. lock.Lock()
  4. lock.Lock()
  5. }
  6. //报死锁错误
  1. func main() {
  2. var lock sync.RWMutex
  3. lock.RLock()
  4. lock.Lock()
  5. }
  6. //报死锁错误
  1. func main() {
  2. var lock sync.RWMutex
  3. lock.RLock()
  4. lock.RLock()
  5. }
  6. //正常执行
1.3.2 sync.WaitGroup

一个不会减少的 WaitGroup 会永久阻塞。

  1. func main() {
  2. var wg sync.WaitGroup
  3. wg.Add(1)
  4. wg.Wait()
  5. //报死锁错误
  6. }
1.3.3 空 select

空 select 会一直阻塞。

  1. package main
  2. func main() {
  3. select {
  4. }
  5. }
  6. //报死锁错误
1.3.4 channel

为 nil 的channel 发送、接受数据都会阻塞。

  1. func main() {
  2. var ch chan struct{}
  3. ch <- struct{}{}
  4. }
  5. //报死锁错误

无缓冲的channel 发送、接受数据都会阻塞。

  1. func main() {
  2. ch := make(chan struct{})
  3. <- ch
  4. }
  5. //报死锁错误

channel 缓冲区满了的,继续发送数据会阻塞。

2、死锁案例讲解

2.1 案例一:空 select{}

  1. package main
  2. func main() {
  3. select {
  4. }
  5. }

以上面为例子,select 语句会 造成 当前 goroutine 阻塞,但是却无法解除阻塞,所以会导致死锁。

2.2 案例二:从无缓冲的channel接受、发送数据

  1. func main() {
  2. ch := make(chan struct{})
  3. //ch <- struct{}{} //发送
  4. <- ch //接受
  5. fmt.Println("main over!")
  6. }

发生原因:

上面创建了一个 名为:ch 的channel,没有缓冲空间。当向无缓存空间的channel 发送或者接受数据时,都会阻塞,但是却无法解除阻塞,所以会导致死锁。

解决方案:边接受边读取

  1. package main
  2. // 方式1
  3. func recv(c chan int) {
  4. ret := <-c
  5. fmt.Println("接收成功", ret)
  6. }
  7. func main() {
  8. ch := make(chan int)
  9. go recv(ch) // 启用goroutine从通道接收值
  10. ch <- 10
  11. fmt.Println("发送成功")
  12. }
  13. // 方式2
  14. func main() {
  15. ch := make(chan int,1)
  16. ch<-1
  17. println(<-ch)
  18. }

2.3 案例三:从空的channel中读取数据

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. func request(index int,ch chan<- string) {
  7. time.Sleep(time.Duration(index)*time.Second)
  8. s := fmt.Sprintf("编号%d完成",index)
  9. ch <- s
  10. }
  11. func main() {
  12. ch := make(chan string, 10)
  13. fmt.Println(ch,len(ch))
  14. for i := 0; i < 4; i++ {
  15. go request(i, ch)
  16. }
  17. for ret := range ch{ //当 ch 中没有数据的时候,for range ch 会发生阻塞,但是无法解除阻塞,发生死锁
  18. fmt.Println(len(ch))
  19. fmt.Println(ret)
  20. }
  21. }

发生原因:

当 ch 中没有数据的时候,就是从空的channel中接受数据,for range ch 会发生阻塞,但是无法解除阻塞,发生死锁。

解决办法:当数据发送完了过后,close channel

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. "time"
  6. )
  7. var wg sync.WaitGroup
  8. func request(index int,ch chan<- string) {
  9. time.Sleep(time.Duration(index)*time.Second)
  10. s := fmt.Sprintf("编号%d完成",index)
  11. ch <- s
  12. wg.Done()
  13. }
  14. func main() {
  15. ch := make(chan string, 10)
  16. for i := 0; i < 4; i++ {
  17. wg.Add(1)
  18. go request(i, ch)
  19. }
  20. go func() {
  21. wg.Wait()
  22. close(ch)
  23. }()
  24. LOOP:
  25. for {
  26. select {
  27. case i,ok := <-ch: // select会一直等待,直到某个case的通信操作完成时,就会执行case分支对应的语句
  28. if !ok {
  29. break LOOP
  30. }
  31. println(i)
  32. default:
  33. time.Sleep(time.Second)
  34. fmt.Println("无数据")
  35. }
  36. }
  37. }

2.4 案例四:给满了的channel发送数据

  1. func main() {
  2. ch := make(chan struct{}, 3)
  3. for i := 0; i < 4; i++ {
  4. ch <- struct{}{}
  5. }
  6. }

发生原因:

ch 是一个带缓冲的channel,但是只能缓冲三个struct,当channel满了过后,继续往channel发送数据会阻塞,但是无法解除阻塞,发生死锁。

解决办法:读取channel中的数据

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. "time"
  6. )
  7. var wg sync.WaitGroup
  8. func main() {
  9. ch := make(chan struct{}, 3)
  10. go func() {
  11. for {
  12. select {
  13. case i, ok := <- ch:
  14. wg.Done()
  15. fmt.Println(i)
  16. if !ok {
  17. return
  18. }
  19. }
  20. }
  21. }()
  22. for i := 0; i < 4; i++ {
  23. wg.Add(1)
  24. ch <- struct{}{}
  25. }
  26. wg.Wait()
  27. }

3、总结

最重要的是记住golang中死锁的触发条件:当 goroutine 发生阻塞,但是无法解除阻塞状态时,就会发生死锁。然后在使用或者阅读代码时,再根据具体情况进行分析。

channel异常情况总结:

注意:对已经关闭的channel再次关闭,也会发生panic。

以上就是我对死锁的思考,有不对的地方恳请指出,谢谢。

golang中关于死锁的思考与学习的更多相关文章

  1. JavaSE中线程与并行API框架学习笔记——线程为什么会不安全?

    前言:休整一个多月之后,终于开始投简历了.这段时间休息了一阵子,又病了几天,真正用来复习准备的时间其实并不多.说实话,心里不是非常有底气. 这可能是学生时代遗留的思维惯性--总想着做好万全准备才去做事 ...

  2. Golang中的自动伸缩和自防御设计

    Raygun服务由许多活动组件构成,每个组件用于特定的任务.其中一个模块是用Golang编写的,负责对iOS崩溃报告进行处理.简而言之,它接受本机iOS崩溃报告,查找相关的dSYM文件,并生成开发者可 ...

  3. Golang 中的 面向对象: 方法, 类, 方法继承, 接口, 多态的简单描述与实现

    前言: Golang 相似与C语言, 基础语法与C基本一致,除了广受争议的 左花括号 必须与代码同行的问题, 别的基本差不多; 学会了C, 基本上万变不离其宗, 现在的高级语言身上都能看到C的影子; ...

  4. SQL Server中解决死锁

    SQL Server中解决死锁的新方法介绍 数据库操作的死锁是不可避免的,本文并不打算讨论死锁如何产生,重点在于解决死锁,通过SQL Server 2005, 现在似乎有了一种新的解决办法. 将下面的 ...

  5. 由SecureCRT引发的思考和学习

    由SecureCRT引发的思考和学习 http://mp.weixin.qq.com/s?__biz=MzAxOTAzMDEwMA==&mid=2652500597&idx=1& ...

  6. Oracle Statspack报告中各项指标含义详解~~学习性能必看!!!

    Oracle Statspack报告中各项指标含义详解~~学习性能必看!!! Data Buffer Hit Ratio#<#90# 数据块在数据缓冲区中的命中率,通常应该在90%以上,否则考虑 ...

  7. Golang中WaitGroup使用的一点坑

    Golang中WaitGroup使用的一点坑 Golang 中的 WaitGroup 一直是同步 goroutine 的推荐实践.自己用了两年多也没遇到过什么问题.直到一天午睡后,同事扔过来一段奇怪的 ...

  8. Golang 中的指针 - Pointer

    http://www.cnblogs.com/jasonxuli/p/6802289.html   Go 的原生数据类型可以分为基本类型和高级类型,基本类型主要包含 string, bool, int ...

  9. HTTPS相关知识以及在golang中的应用

    最近简单学习了HTTPS,并在golang中实践了一下,现在把学到的知识记录下来,方便以后查看,如果有幸能帮到有需要的人就更好了,如果有错误欢迎留言指出. 一些简单的概念,可以自行百度百科 HTTPS ...

  10. SQL Server中解决死锁的新方法介绍

    SQL Server中解决死锁的新方法介绍 数据库操作的死锁是不可避免的,本文并不打算讨论死锁如何产生,重点在于解决死锁,通过SQL Server 2005, 现在似乎有了一种新的解决办法. 将下面的 ...

随机推荐

  1. UniCode 下char*转CString ,利用MultiByteToWideChar进行转换,中文乱码的解决方案

    //计算char *数组大小,以字节为单位,一个汉字占两个字节 int charLen = strlen(sText); //计算多字节字符的大小,按字符计算. int len = MultiByte ...

  2. SQLServer遇到的问题解决方案(6月9日)

    一.判定两个浮点数数值是否相等 对比数据类型为浮点数的数据时,因为浮点数精度问题,当判断两个数值是否相等时往往会出现错误的结果,如下图: 解决方案:一个比较好的解决方案是设定一个精度,通过判定两个值差 ...

  3. 2.常用Dos命令

    #盘符切换 D:   C:#查看当前目录下的所有文件 dir#切换目录 cd change directory    #切换盘cd /d D:切换到D盘    #cd..返回上一级# 清理屏幕  CL ...

  4. go理论知识总结

    基于const常量理解个中类型的内存分配引入参考 官方:Constant expressions may contain only constant operands and are evaluate ...

  5. python基础篇 12-函数+文件读写+json练习作业

    需求: 写一个管理商品的程序,商品文件格式在a.json里面 提供商品的增删改查功能 choice = input('请输入你的选择:1.查看商品 2.新增商品 3.修改商品 4.删除商品') #1. ...

  6. (论文笔记)Deep Neural Network for YouTube Recommendation

    YouTube推荐系统上的深度神经网络 [总结] 在召回模型中,用到的特征比较粗,在训练过程中,目的是训练出一个用户向量u(通过用户本身的浏览和观看信息和统计学信息,假设是N维的),用户向量的用途分两 ...

  7. qt的其他窗口

    一.qt的其他类族 2.Qlabel ui->setupUi(this); QFont font;//确立一个字体对象 font.setFamily("华文行楷");//字体 ...

  8. 正则爬取'豆瓣之乘风破浪的姐姐'的并存入excel文档

    import requests import re import pandas as pd def parse_page(url): headers = { 'User-Agent':'Mozilla ...

  9. Springboot中@Autowired为何获取了我们没有注入的Bean?

    Springboot中@Autowired为何获取了我们没有注入的Bean? 在做仿牛客网项目的时候,有这样一段话: @Autowired private TemplateEngine templat ...

  10. Linux系统管理实战-进程管理

    进程管理 了解进程 状态/生命周期 查看进程 管理进程 kill killall pkill 进程的调度 进程的nice 了解进程状态/生命周期 什么是进程? 进程的状态? 进程的生命周期? 查看进程 ...