天天看点

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

学习channel超时机制时,有下面这段代码

这一段内容详情可见:

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

import (
	"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 <- true
				break
			}
		}

	}()
	for i := 0; i < 3; i++ {
		ch <- i
		time.Sleep(time.Second)
	}
	<-quit
	fmt.Println("程序结束")
}
           

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

package main

import (
	"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 <- true
				break
			}
		}
	}()

	for i := 0; i < 3; i++ {
		ch <- i
		time.Sleep(time.Second * 3)
	}
	<-quit
	fmt.Println("程序结束")
}

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

百度查了一下这个报错的原因:

在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 main

import (
	"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 <- true
				break
			}
		}

	}()
	go func(){
		for {
			<-quit
			fmt.Println("get quit")
		}
	}()

	for i := 0; i < 3; i++ {
		ch <- i
		time.Sleep(time.Second * 3)
	}
	//<-quit
	fmt.Println("程序结束")
}