时间:2021-12-11 09:12:55 | 栏目:Golang | 点击:次
在计算机组成原理里说过 死锁有三个必要条件他们分别是 循环等待、资源共享、非抢占式,在并发中出现通道死锁只有两种情况:
牢记这两点问题就很清晰了,复习下之前的例子,会死锁
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
使用缓冲通道可以让生产者和消费者减少阻塞的可能性,对异步操作更友好,不用等待对方准备,但是容量不应设置过大,不然会占用较多内存。
配对可以让死锁消失,但发送多个值的时候又无法配对了,又会死锁
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
假如我们调换一下位置,把接收放外面,写入放里面会发生什么
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()
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) } }() }
今天我们学习了一些细节,但是相当重要的知识点,也是未来面试高频问题哦!