2016-05-13 152 views
4

這是我正在生成死鎖的程序,我該如何避免它,以及處理這種情況的推薦模式是什麼。如何避免這個golang程序中的死鎖?

問題是超時後如何檢測我的頻道上沒有閱讀器?

var wg sync.WaitGroup 

func main() { 
    wg.Add(1) 
    c := make(chan int) 
    go readFromChannel(c, time.After(time.Duration(2)*time.Second)) 
    time.Sleep(time.Duration(5) * time.Second) 
    c <- 10 
    wg.Wait() 
} 

func readFromChannel(c chan int, ti <-chan time.Time) { 
    select { 
    case x := <-c: 
     fmt.Println("Read", x) 
    case <-ti: 
     fmt.Println("TIMED OUT") 
    } 
    wg.Done() 
} 

回答

1

您有一個無緩衝的頻道。根據docs

如果信道是無緩衝,發送器塊,直到接收器已經接收到 的值。如果信道具有緩衝器,發送方塊 僅直到該值已經被拷貝到緩衝區

通過改變信道到被緩衝,我們能夠避免死鎖。

c := make(chan int, 10) // holds 10 ints 

我也建議您閱讀https://golang.org/doc/effective_go.html#channels,這裏有一些與渠道有關的好東西。

6

因此,讓我們看看你的來源真正發生了什麼。你有兩個 goroutines(有兩個以上,但我們將專注於明確的),mainreadFromChannel

讓我們看看什麼readFromChannel做:

if channel `c` is not empty before `ti` has expired, print its contents and return, after signalling its completion to wait group. 
if `ti` has expired before `c` is not empty, print "TIMED OUT" and return, after signalling its completion to wait group. 

現主營:

adds to waitgroup 
make a channel `c` 
start a goroutine `readFromChannel` 
sleep for 5 seconds 
send 10 to channel `c` 
call wait for waitgroup 

現在,讓我們去通過執行您的代碼流,同時(你的代碼可能/可能不會執行以此順序每次都記住)

1) wg.Add(1) 
2) c := make(chan int) 
3) go readFromChannel(c, time.After(time.Duration(2)*time.Second)) 
#timer ti starts# 
4) time.Sleep(time.Duration(5) * time.Second) 
#MAIN Goroutine begins sleep 
#timer ti expires# 
5) case <-ti: 
6) fmt.Println("TIMED OUT") 
7) wg.Done() 
# readFromChannel Goroutine returns # 
#MAIN Goroutine exits sleep# 
8) c<-10 
9) ......#DEADLOCK# 

現在你可以gue ss爲什麼你會陷入僵局。在進行中,無緩衝通道將阻止,直到通道的另一端發生某些事情,無論您是發送還是接收。因此c <- 10會阻塞,直到從c的另一端讀取某些內容爲止,但您爲此所用的參數在2秒前已經退出畫面。因此,c永遠是塊,並且因爲main是最後一個goroutine,你會得到一個死鎖。

如何預防?使用頻道時,請確保每個send頻道的另一端總是有一個receive。您也可以使用緩衝通道,但在上面的代碼中,它不會是「正確的」解決方案。

這裏是我的僵局的解決辦法:

func main() { 
    wg.Add(1) 
    c := make(chan int) 
    go readFromChannel(c, time.After(time.Duration(2)*time.Second)) 
    time.Sleep(time.Duration(5) * time.Second) 
    c <- 10 
    wg.Wait() 
} 

func readFromChannel(c chan int, ti <-chan time.Time) { 
     // the forloop will run forever 
    loop: // ** 
    for { 
     select { 
      case x := <-c: 
        fmt.Println("Read", x) 
        break loop // breaks out of the for loop and the select ** 
      case <-ti: 
        fmt.Println("TIMED OUT") 
      } 
    } 
    wg.Done() 
} 

** see this answer for details

+0

這是我在一段時間內閱讀最有用的答案之一。只是爲了確保我遵循:「修復」的作品,因爲它保持接收器頻道即使超時。所以'wg.Done()'(並終止''main'例程)只會在從'c'讀入內容時纔會發生,對吧? –

+1

沒錯,但爲了清理一下,它保持** goroutine **運行。 – AJPennster

0

你的問題是,你正在使用select聲明,但沒有使用一個夠程內。

go func() { 
    for { 
     select { 
     case x := <-c: 
      fmt.Println("Read", x) 
     case <-ti: 
      fmt.Println("TIMED OUT") 
     } 
    } 
}() 

走出不同的併發執行夠程的值可以選擇關鍵字,它非常類似於開關控制語句,有時也被稱爲通信開關來完成。

在默認情況下在select語句中使用發送操作可以保證發送是非阻塞的!如果沒有這種情況,選擇將永遠執行。

https://play.golang.org/p/Ai1ggveb4s

0

這是一個老問題,但我潛水深入學習渠道自己,發現這個在這裏。

我想你只需要在完成發送後關閉頻道?

代碼:

func main() { 
    wg.Add(1) 
    c := make(chan int) 
    go readFromChannel(c, time.After(time.Duration(2)*time.Second)) 
    time.Sleep(time.Duration(5) * time.Second) 
    c <- 10 
    close(c) // <- CLOSE IT HERE 
    wg.Wait() 
}