golang中关于死锁的思考与学习

news/2024/9/20 1:28:18/文章来源:https://www.cnblogs.com/zhanchenjin/p/18404392

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中死锁的触发条件:

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

1.3 golang 中阻塞的场景

1.3.1 sync.Mutex、sync.RWMutex

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

写写冲突,读写冲突,读读不冲突。
func main() {var lock sync.Mutexlock.Lock()lock.Lock()
}   
//报死锁错误
func main() {var lock sync.RWMutexlock.RLock()lock.Lock()
}
//报死锁错误
func main() {var lock sync.RWMutexlock.RLock()lock.RLock()
}
//正常执行

1.3.2 sync.WaitGroup

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

func main() {var wg sync.WaitGroupwg.Add(1)wg.Wait()//报死锁错误
}

1.3.3 空 select

空 select 会一直阻塞。

package mainfunc main() {select {}
}
//报死锁错误

1.3.4 channel

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

func main() {var ch chan struct{}ch <- struct{}{}
}
//报死锁错误

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

func main() {ch := make(chan struct{})<- ch
}
//报死锁错误

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

2、死锁案例讲解

2.1 案例一:空 select{}

package mainfunc main() {select {}
}

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

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

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

发生原因:

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

解决方案:边接受边读取

package main// 方式1
func recv(c chan int) {ret := <-cfmt.Println("接收成功", ret)
}
func main() {ch := make(chan int)go recv(ch) // 启用goroutine从通道接收值ch <- 10fmt.Println("发送成功")
}// 方式2
func main() {ch := make(chan int,1)ch<-1println(<-ch)
}

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

package mainimport ("fmt""time"
)func request(index int,ch chan<- string)  {time.Sleep(time.Duration(index)*time.Second)s := fmt.Sprintf("编号%d完成",index)ch <- s
}func main() {ch := make(chan string, 10)fmt.Println(ch,len(ch))for i := 0; i < 4; i++ {go request(i, ch)}for ret := range ch{ //当 ch 中没有数据的时候,for range ch 会发生阻塞,但是无法解除阻塞,发生死锁fmt.Println(len(ch))fmt.Println(ret)}
}

发生原因:

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

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

package mainimport ("fmt""sync""time"
)var wg sync.WaitGroupfunc request(index int,ch chan<- string)  {time.Sleep(time.Duration(index)*time.Second)s := fmt.Sprintf("编号%d完成",index)ch <- swg.Done()
}func main() {ch := make(chan string, 10)for i := 0; i < 4; i++ {wg.Add(1)go request(i, ch)}go func() {wg.Wait()close(ch)}()LOOP:for {select {case i,ok := <-ch: // select会一直等待,直到某个case的通信操作完成时,就会执行case分支对应的语句if !ok {break LOOP}println(i)default:time.Sleep(time.Second)fmt.Println("无数据")}}
}

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

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

发生原因:

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

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

package mainimport ("fmt""sync""time"
)var wg sync.WaitGroupfunc main() {ch := make(chan struct{}, 3)go func() {for {select {case i, ok := <- ch:wg.Done()fmt.Println(i)if !ok {return}}}}()for i := 0; i < 4; i++ {wg.Add(1)ch <- struct{}{}}wg.Wait()
}

3、总结

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

channel异常情况总结:

 

 

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/794443.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ASP.NET 8 AOT 遍历文件夹获取文件名称及时长

朋友下了N多视频记不住文件夹下都有啥了…问批处理怎么导出文件夹下所有文件名及视频时长…批处理导出文件名好弄,获取视频时长真没用过…想到 .net 控制台程序 AOT 发布,不和批处理差不多效果…新建个控制台项目选下使用AOT 首先要获取文件的时长,我们需要用下 TagLibSharp…

公安网络信息体系能力分类

来源:中国电子科学研究院学报,《公安网络信息体系顶层设计》

轮胎充气压力 气压单位换算 All In One

轮胎充气压力 & 气压单位换算 All In One bar / psi 1 bar = 14.5 psi轮胎充气压力 & 气压单位换算 All In Onebar / psi气压单位换算 1 bar = 14.5 psi demos(🐞 反爬虫测试!打击盗版⚠️)如果你看到这个信息, 说明这是一篇剽窃的文章,请访问 https://www.cnblog…

【工具推荐】xxl-jobExploitGUI - xxl-job一键漏洞利用工具+一键getshll

工具介绍 xxl-jobExploitGUI实现了XXL-JOB默认accessToken权限绕过漏洞的单个检测、批量检测、一键反弹shell功能,后续会持续更新优化,添加POC检测等。 下载链接: 链接: https://pan.quark.cn/s/55ed00b1512d使用说明 单个检测批量检测 fofa语句: body="{\"code\…

电动自行车轮胎规格参数图解教程 All In One

电动自行车轮胎规格参数图解教程 All In One电动自行车轮胎规格参数图解教程 All In One电动车轮胎参数单位换算1 in / 1 英寸 => 2.54 cm / 2.54 厘米https://convertlive.com/zh/u/转换/英寸/自/厘米#10 轮胎参数 轮胎尺寸(英寸): 轮毂尺寸(英寸): 10in 轮胎宽度/断面宽度…

首发找大佬定制的 黑神话悟空内置修改器

无敌模式 无伤判定 无限生命 无限法力 无限气力 无限棍势各种几十个功能 选择游戏进程直接起飞 自己打气死了一直过不去 直接找大佬定制一个 不想让兄弟们受气了 不限速链接:https://pan.quark.cn/s/f4f826c88f1d本文来自博客园,作者:Mr小扎仙森,转载请注明原文链接:http…

【工具推荐】TPscan (最新版本) - 一键ThinkPHP漏洞检测getshell

工具介绍 一键ThinkPHP漏洞检测,基于Python3,一键getshell 下载链接: 链接: https://pan.quark.cn/s/ff51139a5ace使用说明看着就真的看着,不学就真的5

强制删除PG数据库

删除数据是否显示: 有会话正在使用数据库: 解决: 一、查看谁在使用此数据库:SELECT * FROM pg_stat_activity WHERE usename = user where datname = your_database_name; 这将返回当前用户的所有活跃连接的详细信息。 二、如果可以最好先把占用的会话关掉后再删除 三、强…

训练框架技术序列一:Megtron-LLM架构源码

本文章涉及的Megatron-llm的XMind思维导图源文件和PDF文件,可在网盘下载: https://pan.baidu.com/s/1xRZD-IP95y7-4Fn0C_VJMg 提取码: qxff一、引言 Megatron-Core 是一个基于 PyTorch 的开源库,专为在 NVIDIA GPU 上高效训练大型语言模型(LLMs)而设计。它提供了一系列 GP…

KernelWarehouse:英特尔开源轻量级涨点神器,动态卷积核突破100+ | ICML 2024

动态卷积学习n个静态卷积核的线性混合,加权使用它们输入相关的注意力,表现出比普通卷积更优越的性能。然而,它将卷积参数的数量增加了n倍,因此并不是参数高效的。这导致不能探索n>100的设置(比典型设置n<10大一个数量级),推动动态卷积性能边界提升的同时享受参数的…

小集训 CSP-S 模拟赛

DAY 1 A.喜剧的迷人之处在于 小思维题不必细讲 B. 镜中的野兽 状压+容斥 $ gcd (x) + lcm(x) = m $ ,可以得知 $ gcd(x) $ 一定是 m 的因子,那么就可以枚举 $ gcd(x) $ 和 $ lcm(x) $。 对于已经确定的一对 $ gcd (x) 和 lcm(x) $ ,将他们进行质因数分解,写成 $ \prod{p_{…

从STM32的定时器到Linux上的时间相关服务

难题:在baremetal上实现按钮点击、长按事件 起因是因为我想用stm32加几个按钮只做一个我自己的控制器,我可以通过按钮执行一些功能。 硬件是如何和CPU通信的呢?CPU上支出几个GPIO引脚,这些引脚可以配置为输入模式和输出模式,并且都有两种状态——高电平和低电平。硬件连接…