Go channel阻塞,死锁, fatal error: all goroutines are asleep - deadlock!

在使用channel消息通道的时候不注意的话,就会出现死锁的情况,这种情况的原因一般都是并发执行的时候,一个协程对A通道做写入或读取操作,而另一个协程对A做对应的操作,同时A的缓存用完了或者没有设置缓存,这时候就会导致两个协程互相等待对方操作,成为死锁.

    c1 := make(chan int)
    c2 := make(chan int)
    c3 := make(chan [2]int)
    go func() {
        for {
            fmt.Println("Loop . . .")
            select {
            case i := <-c1 :
                fmt.Println("Got num from C1", i)
                c3 <- [2]int{1, i}
            case j := <-c2:
                fmt.Println("Got num from C2", j)
                c3 <- [2]int{2, j}
            default:
                c3 <- [2]int{2, 0}
            }
            fmt.Println("Sleep . . .")
            time.Sleep(time.Second)
        }
    }()
    for  {
        fmt.Println("Insert into Channel01")
        c1 <- 13
        fmt.Println("Insert into Channel02")
        c2 <- 14
        fmt.Println("Insert into Channel03")
        for sign := range c3 {
           fmt.Println("got from c3:", sign)
           c1 <- 23
           c2 <- 24
        }
    }

如上就会出现死锁
channel定义的时候可以设置为有缓存和无缓存,通过make的第二个参数指定,上面的代码都没有指定该参数,所以创建的channel为无缓存的.
无缓存channel的特性为,一个通道不会保存任何数据,任一协程往里面放数据,都必须等待另一个使用该通道数据的协程从里面拿走,或者协程从里面拿数据的时候,必须有另一个协程往里面放东西,由于没有容量,所以放数据的协程必须等待放入的数据被消费才会执行后面的代码.举个例子就是你和另一个人协同工作,现在你要把一个东西交给另一个人,你把这个东西放在手上,如果另一个人不从你手里拿走的话,你就干不了剩下的事.

如图,两个协程分别是1和2
协程1走到1-1的时候,会等待C1出现数据,在协程2的2-1会放入数据,这时候协程1可以往下走,执行打印,然后等待往c3放数据,但是协程2走完2-1之后,在2-2会往c2中放数据,由于三个通道都没缓存,数据必须是及时消费的,c2在2-2的时候放入数据就会等待消费,不会往下执行,直到c2的数据被消费为止.但是协程1的1-2步骤也和2-2一样,等待被消费,这时候两个协程同时阻塞了.
能够打破同时阻塞的代码在1-3,也就是c2的数据被消费,但是这一步要等待1-2步骤完成,由于1-2操作的c3一直没有消费者,所以1-3就永远走不到了.2-2走不下去,同样下面对c3的消费也不会走到.

这就导致了死锁的情况.

执行结果如上图

解决办法有两个,一个是及时消费,但是这涉及到代码逻辑,不可能强行消费,另一个办法就是创建channel的时候设定缓存值.
相当于在两个协程之间放了一个桌子,这样A可以把数据放在桌子上,然后继续做自己的事,B需要数据可以从桌子上拿,当然如果拿数据的协程拿不到数据,同样还会阻塞,这里可以用select来处理,执行default来避免阻塞.
如图:

改成这样,即使容量只设置成1,程序也会执行下去,不会再被阻塞:

posted @ 2021-11-30 11:04  华腾海神  阅读(559)  评论(0编辑  收藏  举报