当前位置: 代码迷 >> 综合 >> 【Go】channel超时机制触发的(fatal error: all goroutines are asleep - deadlock!)
  详细解决方案

【Go】channel超时机制触发的(fatal error: all goroutines are asleep - deadlock!)

热度:80   发布时间:2023-11-25 07:12:55.0

学习channel超时机制时,有下面这段代码
这一段内容详情可见:

http://c.biancheng.net/view/4361.html

package mainimport ("fmt""time"
)func main() {
    ch := make(chan int)quit := make(chan bool)//新开一个协程go func() {
    for {
    select {
    case num := <-ch:fmt.Println("num = ", num)case <-time.After(2 * time.Second):fmt.Println("超时")quit <- truebreak}}}()for i := 0; i < 3; i++ {
    ch <- itime.Sleep(time.Second)}<-quitfmt.Println("程序结束")
}

之后,我把代码改了一下,把for循环睡的时间改成了3秒,想看看直接触发超时,结果就出问题了

package mainimport ("fmt""time"
)func main() {
    ch := make(chan int)quit := make(chan bool)//新开一个协程go func() {
    for {
    select {
    case num := <-ch:fmt.Println("num = ", num)case <-time.After(2 * time.Second):fmt.Println("超时")quit <- truebreak}}}()for i := 0; i < 3; i++ {
    ch <- itime.Sleep(time.Second * 3)}<-quitfmt.Println("程序结束")
}

在这里插入图片描述
百度查了一下这个报错的原因:

在main goroutine线,期望从管道中获得一个数据,而这个数据必须是其他goroutine线放入管道的
但是其他goroutine线都已经执行完了(all goroutines are asleep),那么就永远不会有数据放入管道。
所以,main goroutine线在等一个永远不会来的数据,那整个程序就永远等下去了。
这显然是没有结果的,所以这个程序就说“算了吧,不坚持了,我自己自杀掉,报一个错给代码作者,我被deadlock了”

回过头来看改之后的代码,for循环中沉睡了3s,所以<-quit是还没有执行到的,也就是说quit通道的信息没有接收端。而协程中,由于沉睡触发的往quit通道发送信息就因为缺少接收端,所以出现上面的报错。

解决方法有两种:
1.把quit通道改为有缓存空间的,让它在还未接收时,信息能先发到缓存空间。也就是改为
quit := make(chan bool, 2)
这里for循环中有3次,但实验发现,缓存空间为2就行了。我猜测应该是第三次的时候,还未来的及执行发送信号,main主协程中就执行到了quit的接收端。
2.把quit通道的接收端也放在一个协程里面,这样就不受main逻辑的影响。下面展示本方法的代码。

package mainimport ("fmt""time"
)func main() {
    ch := make(chan int)quit := make(chan bool)//新开一个协程go func() {
    for {
    select {
    case num := <-ch:fmt.Println("num = ", num)case <-time.After(2 * time.Second):fmt.Println("超时")quit <- truebreak}}}()go func(){
    for {
    <-quitfmt.Println("get quit")}}()for i := 0; i < 3; i++ {
    ch <- itime.Sleep(time.Second * 3)}//<-quitfmt.Println("程序结束")
}
  相关解决方案