1. 程式人生 > 其它 >【Go】channel超時機制觸發的(fatal error: all goroutines are asleep - deadlock!)

【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("程式結束") }

在這裡插入圖片描述
百度查了一下這個報錯的原因:

在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("程式結束")
}