Go select 死鎖的一個細節(jié)
下面對是一個 select 死鎖的問題
package main
import "sync"
func main() {
var wg sync.WaitGroup
foo := make(chan int)
bar := make(chan int)
wg.Add(1)
go func() {
defer wg.Done()
select {
case foo <- <-bar:
default:
println("default")
}
}()
wg.Wait()
}
按常規(guī)理解,go func 中的 select 應該執(zhí)行 default 分支,程序正常運行。但結(jié)果卻不是,而是死鎖。可以通過該鏈接測試:https://play.studygolang.com/p/kF4pOjYXbXf。
原因文章也解釋了,Go 語言規(guī)范中有這么一句:
For all the cases in the statement, the channel operands of receive operations and the channel and right-hand-side expressions of send statements are evaluated exactly once, in source order, upon entering the “select” statement. The result is a set of channels to receive from or send to, and the corresponding values to send. Any side effects in that evaluation will occur irrespective of which (if any) communication operation is selected to proceed. Expressions on the left-hand side of a RecvStmt with a short variable declaration or assignment are not yet evaluated.
不知道大家看懂沒有?于是,最后來了一個例子驗證你是否理解了:為什么每次都是輸出一半數(shù)據(jù),然后死鎖?(同樣,這里可以運行查看結(jié)果:https://play.studygolang.com/p/zoJtTzI7K5T)
package main
import (
"fmt"
"time"
)
func talk(msg string, sleep int) <-chan string {
ch := make(chan string)
go func() {
for i := 0; i < 5; i++ {
ch <- fmt.Sprintf("%s %d", msg, i)
time.Sleep(time.Duration(sleep) * time.Millisecond)
}
}()
return ch
}
func fanIn(input1, input2 <-chan string) <-chan string {
ch := make(chan string)
go func() {
for {
select {
case ch <- <-input1:
case ch <- <-input2:
}
}
}()
return ch
}
func main() {
ch := fanIn(talk("A", 10), talk("B", 1000))
for i := 0; i < 10; i++ {
fmt.Printf("%q\n", <-ch)
}
}
有沒有這種感覺:

這是 StackOverflow 上的一個問題:https://stackoverflow.com/questions/51167940/chained-channel-operations-in-a-single-select-case。
關鍵點和文章開頭例子一樣,在于 select case 中兩個 channel 串起來,即 fanIn 函數(shù)中:
select {
case ch <- <-input1:
case ch <- <-input2:
}
如果改為這樣就一切正常:
select {
case t := <-input1:
ch <- t
case t := <-input2:
ch <- t
}
結(jié)合這個更復雜的例子分析 Go 語言規(guī)范中的那句話。
對于 select 語句,在進入該語句時,會按源碼的順序?qū)γ恳粋€ case 子句進行求值:這個求值只針對發(fā)送或接收操作的額外表達式。
比如:
// ch 是一個 chan int;
// getVal() 返回 int
// input 是 chan int
// getch() 返回 chan int
select {
case ch <- getVal():
case ch <- <-input:
case getch() <- 1:
case <- getch():
}
在沒有選擇某個具體 case 執(zhí)行前,例子中的 getVal() 、 <-input 和 getch() 會執(zhí)行。這里有一個驗證的例子:https://play.studygolang.com/p/DkpCq3aQ1TE。
package main
import (
"fmt"
)
func main() {
ch := make(chan int)
go func() {
select {
case ch <- getVal(1):
fmt.Println("in first case")
case ch <- getVal(2):
fmt.Println("in second case")
default:
fmt.Println("default")
}
}()
fmt.Println("The val:", <-ch)
}
func getVal(i int) int {
fmt.Println("getVal, i=", i)
return i
}
無論 select 最終選擇了哪個 case, getVal() 都會按照源碼順序執(zhí)行: getVal(1) 和 getVal(2) ,也就是它們必然先輸出:
getVal, i= 1 getVal, i= 2
你可以仔細琢磨一下。
現(xiàn)在回到 StackOverflow 上的那個問題。
每次進入以下 select 語句時:
select {
case ch <- <-input1:
case ch <- <-input2:
}
<-input1 和 <-input2 都會執(zhí)行,相應的值是:A x 和 B x(其中 x 是 0-5)。但每次 select 只會選擇其中一個 case 執(zhí)行,所以 <-input1 和 <-input2 的結(jié)果,必然有一個被丟棄了,也就是不會被寫入 ch 中。因此,一共只會輸出 5 次,另外 5 次結(jié)果丟掉了。(你會發(fā)現(xiàn),輸出的 5 次結(jié)果中,x 比如是 0 1 2 3 4)
而 main 中循環(huán) 10 次,只獲得 5 次結(jié)果,所以輸出 5 次后,報死鎖。
雖然這是一個小細節(jié),但實際開發(fā)中還是有可能出現(xiàn)的。比如文章提到的例子寫法:
// ch 是一個 chan int;
// getVal() 返回 int
// input 是 chan int
// getch() 返回 chan int
select {
case ch <- getVal():
case ch <- <-input:
case getch() <- 1:
case <- getch():
}
因此在使用 select 時,一定要注意這種可能的問題。
不要以為這個問題不會遇到,其實很常見。最多的就是 time.After 導致內(nèi)存泄露問題,網(wǎng)上有很多文章解釋原因,如何避免,其實最根本原因就是因為 select 這個機制導致的。
比如如下代碼,有內(nèi)存泄露(傳遞給 time.After 的時間參數(shù)越大,泄露會越厲害),你能解釋原因嗎?
package main
import (
"time"
)
func main() {
ch := make(chan int, 10)
go func() {
var i = 1
for {
i++
ch <- i
}
}()
for {
select {
case x := <- ch:
println(x)
case <- time.After(30 * time.Second):
println(time.Now().Unix())
}
}
}
到此這篇關于Go select 死鎖的一個細節(jié)的文章就介紹到這了,更多相關Go select 死鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Go語言數(shù)據(jù)結(jié)構(gòu)之二叉樹必會知識點總結(jié)
如果你是一個開發(fā)人員,或多或少對樹型結(jié)構(gòu)都有一定的認識。二叉樹作為樹的一種,是一種重要的數(shù)據(jù)結(jié)構(gòu),也是面試官經(jīng)??嫉臇|西。本文為大家總結(jié)了一些二叉樹必會知識點,需要的可以參考一下2022-08-08

