★本节源码位置 https://github.com/golang-minibear2333/golang/blob/master/4.concurrent/4.4-deadlock/ ”
在计算机组成原理里说过 死锁有三个必要条件他们分别是 循环等待、资源共享、非抢占式,在并发中出现通道死锁只有两种情况:
牢记这两点问题就很清晰了,复习下之前的例子,会死锁
a := make(chan int)
a <- 1 //将数据写入channel
z := <-a //从channel中读取数据
解决方法一,协程配对,先发送还是先接收无所谓只要配对就好
chanInt := make(chan int)
go func() {
chanInt <- 1
}()
res := <-chanInt
解决方法二,缓冲通道
chanInt := make(chan int,1)
chanInt <- 2
res := <-chanInt
len()
函数可以测试出来cap()
测试出来cap>len
时候,因为没有满,发送不会阻塞len>0
时,因为不为空,所以接收不会阻塞使用缓冲通道可以让生产者和消费者减少阻塞的可能性,对异步操作更友好,不用等待对方准备,但是容量不应设置过大,不然会占用较多内存。
配对可以让死锁消失,但发送多个值的时候又无法配对了,又会死锁
func multipleDeathLock() {
chanInt := make(chan int)
defer close(chanInt)
go func() {
res := <-chanInt
fmt.Println(res)
}()
chanInt <- 1
chanInt <- 1
}
不出所料死锁了
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.multipleDeathLock()
只有在工作中通知信号是一对一的情况,通知一次以后就不再使用了,其他这种要求多次读写配对的情况根本不会存在。
更常见的是用循环来不断接收值,接受一个处理一个,如下:
func multipleLoop() {
chanInt := make(chan int)
defer close(chanInt)
go func() {
for {
//不使用ok会goroutine泄漏
//res := <-chanInt
res,ok := <-chanInt
if !ok {
break
}
fmt.Println(res)
}
}()
chanInt <- 1
chanInt <- 1
}
输出:
1
1
ok
代表通道是否正常,如果是关闭则为false
值1 2 0 0 0
这样的数列,因为关闭是需要时间的,而循环接收关闭的通道拿到的是0
goroutine
泄漏稍后会讲到假如我们调换一下位置,把接收放外面,写入放里面会发生什么
func multipleDeathLock2() {
chanInt := make(chan int)
defer close(chanInt)
go func() {
chanInt <- 1
chanInt <- 2
}()
for {
res, ok := <-chanInt
if !ok {
break
}
fmt.Println(res)
}
}
输出死锁
1
2
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.multipleDeathLock2()
for
循环一直在获取通道中的值,但是在读取完1 2
后,通道中没有新的值传入,这样接收者就阻塞了。defer
自动关闭通道goroutine
终止的场景有三个:
goroutine
完成了它的工作当三个条件都没有满足,goroutine
就会一直运行下去
func goroutineLeak() {
chanInt := make(chan int)
defer close(chanInt)
go func() {
for {
res := <-chanInt
//res,ok := <-chanInt
//if !ok {
// break
//}
fmt.Println(res)
}
}()
chanInt <- 1
chanInt <- 1
}
goroutineLeak()
函数结束后触发defer close(chanInt)
关闭了通道goroutine
并没有关闭,而是一直在循环取值,并且取到是的关闭后的通道值(这里是int
的默认值 0)goroutine
会永远运行下去,如果以后再次使用又会出现新的泄漏!导致内存、cpu
占用越来越多输出,如果程序不停止就会一直输出0
1
1
0
0
0
...
假如不关闭且外部没有写入值,那接收处就会永远阻塞在那里,连输出都不会有
func goroutineLeakNoClosed() {
chanInt := make(chan int)
go func() {
for {
res := <-chanInt
fmt.Println(res)
}
}()
}
goroutine
进入死循环也是泄露的原因使用 golang 自带的pprof
监控工具,可以发现内存上涨情况,这个后续会讲
还可以监控进程的内存使用情况,比如prometheus
提供的process-exporter
如果你有内存泄露/goroutine 泄露代码扫描的工具,欢迎留言,感恩!
今天我们学习了一些细节,但是相当重要的知识点,也是未来面试高频问题哦!
goroutine
进入死循环也是泄露的原因后续文章更新请点击阅读原文跳转开源书
(点击上方卡片关注我持续更新优质文章)
本文分享自 roseduan写字的地方 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!