24. Select

24. Select

什么是 select?

select 語(yǔ)句用于在多個(gè)發(fā)送/接收信道操作中進(jìn)行選擇此改。select 語(yǔ)句會(huì)一直阻塞,直到發(fā)送/接收操作準(zhǔn)備就緒。如果有多個(gè)信道操作準(zhǔn)備完畢另萤,select 會(huì)隨機(jī)地選取其中之一執(zhí)行。該語(yǔ)法與 switch 類(lèi)似诅挑,所不同的是四敞,這里的每個(gè) case 語(yǔ)句都是信道操作。我們好好看一些代碼來(lái)加深理解吧拔妥。

示例

package main

import (  
    "fmt"
    "time"
)

func server1(ch chan string) {  
    time.Sleep(6 * time.Second)
    ch <- "from server1"
}
func server2(ch chan string) {  
    time.Sleep(3 * time.Second)
    ch <- "from server2"

}
func main() {  
    output1 := make(chan string)
    output2 := make(chan string)
    go server1(output1)
    go server2(output2)
    select {
    case s1 := <-output1:
        fmt.Println(s1)
    case s2 := <-output2:
        fmt.Println(s2)
    }
}

在上面程序里忿危,server1 函數(shù)(第 8 行)休眠了 6 秒,接著將文本 from server1 寫(xiě)入信道 ch没龙。而 server2 函數(shù)(第 12 行)休眠了 3 秒铺厨,然后把 from server2 寫(xiě)入了信道 ch

main 函數(shù)在第 20 行和第 21 行硬纤,分別調(diào)用了 server1server2 兩個(gè) Go 協(xié)程解滓。

在第 22 行,程序運(yùn)行到了 select 語(yǔ)句咬摇。select 會(huì)一直發(fā)生阻塞伐蒂,除非其中有 case 準(zhǔn)備就緒。在上述程序里肛鹏,server1 協(xié)程會(huì)在 6 秒之后寫(xiě)入 output1 信道逸邦,而server2 協(xié)程在 3 秒之后就寫(xiě)入了 output2 信道。因此 select 語(yǔ)句會(huì)阻塞 3 秒鐘在扰,等著 server2output2 信道寫(xiě)入數(shù)據(jù)缕减。3 秒鐘過(guò)后,程序會(huì)輸出:

from server2

然后程序終止芒珠。

select 的應(yīng)用

在上面程序中桥狡,函數(shù)之所以取名為 server1server2,是為了展示 select 的實(shí)際應(yīng)用皱卓。

假設(shè)我們有一個(gè)關(guān)鍵性應(yīng)用裹芝,需要盡快地把輸出返回給用戶(hù)。這個(gè)應(yīng)用的數(shù)據(jù)庫(kù)復(fù)制并且存儲(chǔ)在世界各地的服務(wù)器上娜汁。假設(shè)函數(shù) server1server2 與這樣不同區(qū)域的兩臺(tái)服務(wù)器進(jìn)行通信嫂易。每臺(tái)服務(wù)器的負(fù)載和網(wǎng)絡(luò)時(shí)延決定了它的響應(yīng)時(shí)間。我們向兩臺(tái)服務(wù)器發(fā)送請(qǐng)求掐禁,并使用 select 語(yǔ)句等待相應(yīng)的信道發(fā)出響應(yīng)怜械。select 會(huì)選擇首先響應(yīng)的服務(wù)器颅和,而忽略其它的響應(yīng)。使用這種方法缕允,我們可以向多個(gè)服務(wù)器發(fā)送請(qǐng)求峡扩,并給用戶(hù)返回最快的響應(yīng)了。:)

默認(rèn)情況

在沒(méi)有 case 準(zhǔn)備就緒時(shí)障本,可以執(zhí)行 select 語(yǔ)句中的默認(rèn)情況(Default Case)教届。這通常用于防止 select 語(yǔ)句一直阻塞。

package main

import (  
    "fmt"
    "time"
)

func process(ch chan string) {  
    time.Sleep(10500 * time.Millisecond)
    ch <- "process successful"
}

func main() {  
    ch := make(chan string)
    go process(ch)
    for {
        time.Sleep(1000 * time.Millisecond)
        select {
        case v := <-ch:
            fmt.Println("received value: ", v)
            return
        default:
            fmt.Println("no value received")
        }
    }

}

上述程序中彼绷,第 8 行的 process 函數(shù)休眠了 10500 毫秒(10.5 秒)巍佑,接著把 process successful 寫(xiě)入 ch 信道。在程序中的第 15 行寄悯,并發(fā)地調(diào)用了這個(gè)函數(shù)萤衰。

在并發(fā)地調(diào)用了 process 協(xié)程之后,主協(xié)程啟動(dòng)了一個(gè)無(wú)限循環(huán)猜旬。這個(gè)無(wú)限循環(huán)在每一次迭代開(kāi)始時(shí)脆栋,都會(huì)先休眠 1000 毫秒(1 秒),然后執(zhí)行一個(gè) select 操作洒擦。在最開(kāi)始的 10500 毫秒中椿争,由于 process 協(xié)程在 10500 毫秒后才會(huì)向 ch 信道寫(xiě)入數(shù)據(jù),因此 select 語(yǔ)句的第一個(gè) case(即 case v := <-ch:)并未就緒熟嫩。所以在這期間秦踪,程序會(huì)執(zhí)行默認(rèn)情況,該程序會(huì)打印 10 次 no value received掸茅。

在 10.5 秒之后椅邓,process 協(xié)程會(huì)在第 10 行向 ch 寫(xiě)入 process successful。現(xiàn)在昧狮,就可以執(zhí)行 select 語(yǔ)句的第一個(gè) case 了景馁,程序會(huì)打印 received value: process successful,然后程序終止逗鸣。該程序會(huì)輸出:

no value received  
no value received  
no value received  
no value received  
no value received  
no value received  
no value received  
no value received  
no value received  
no value received  
received value:  process successful

死鎖與默認(rèn)情況

package main

func main() {  
    ch := make(chan string)
    select {
    case <-ch:
    }
}

上面的程序中合住,我們?cè)诘?4 行創(chuàng)建了一個(gè)信道 ch。我們?cè)?select 內(nèi)部(第 6 行)撒璧,試圖讀取信道 ch透葛。由于沒(méi)有 Go 協(xié)程向該信道寫(xiě)入數(shù)據(jù),因此 select 語(yǔ)句會(huì)一直阻塞卿樱,導(dǎo)致死鎖僚害。該程序會(huì)觸發(fā)運(yùn)行時(shí) panic,報(bào)錯(cuò)信息如下:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:  
main.main()  
    /tmp/sandbox416567824/main.go:6 +0x80

如果存在默認(rèn)情況殿如,就不會(huì)發(fā)生死鎖贡珊,因?yàn)樵跊](méi)有其他 case 準(zhǔn)備就緒時(shí),會(huì)執(zhí)行默認(rèn)情況涉馁。我們用默認(rèn)情況重寫(xiě)后门岔,程序如下:

package main

import "fmt"

func main() {  
    ch := make(chan string)
    select {
    case <-ch:
    default:
        fmt.Println("default case executed")
    }
}

以上程序會(huì)輸出:

default case executed

如果 select 只含有值為 nil 的信道,也同樣會(huì)執(zhí)行默認(rèn)情況烤送。

package main

import "fmt"

func main() {  
    var ch chan string
    select {
    case v := <-ch:
        fmt.Println("received value", v)
    default:
        fmt.Println("default case executed")

    }
}

在上面程序中寒随,ch 等于 nil,而我們?cè)噲D在 select 中讀取 ch(第 8 行)帮坚。如果沒(méi)有默認(rèn)情況妻往,select 會(huì)一直阻塞,導(dǎo)致死鎖试和。由于我們?cè)?select 內(nèi)部加入了默認(rèn)情況讯泣,程序會(huì)執(zhí)行它,并輸出:

default case executed

隨機(jī)選取

當(dāng) select 由多個(gè) case 準(zhǔn)備就緒時(shí)阅悍,將會(huì)隨機(jī)地選取其中之一去執(zhí)行好渠。

package main

import (  
    "fmt"
    "time"
)

func server1(ch chan string) {  
    ch <- "from server1"
}
func server2(ch chan string) {  
    ch <- "from server2"

}
func main() {  
    output1 := make(chan string)
    output2 := make(chan string)
    go server1(output1)
    go server2(output2)
    time.Sleep(1 * time.Second)
    select {
    case s1 := <-output1:
        fmt.Println(s1)
    case s2 := <-output2:
        fmt.Println(s2)
    }
}

在上面程序里,我們?cè)诘?18 行和第 19 行分別調(diào)用了 server1server2 兩個(gè) Go 協(xié)程节视。接下來(lái)拳锚,主程序休眠了 1 秒鐘(第 20 行)。當(dāng)程序控制到達(dá)第 21 行的 select 語(yǔ)句時(shí)寻行,server1 已經(jīng)把 from server1 寫(xiě)到了 output1 信道上霍掺,而 server2 也同樣把 from server2 寫(xiě)到了 output2 信道上。因此這個(gè) select 語(yǔ)句中的兩種情況都準(zhǔn)備好執(zhí)行了拌蜘。如果你運(yùn)行這個(gè)程序很多次的話杆烁,輸出會(huì)是 from server1 或者 from server2,這會(huì)根據(jù)隨機(jī)選取的結(jié)果而變化拦坠。

請(qǐng)?jiān)谀愕谋镜叵到y(tǒng)上運(yùn)行這個(gè)程序连躏,獲得程序的隨機(jī)結(jié)果。因?yàn)槿绻阍?playground 上在線運(yùn)行的話贞滨,它的輸出總是一樣的入热,這是由于 playground 不具有隨機(jī)性所造成的。

這下我懂了:空 select

package main

func main() {  
    select {}
}

你認(rèn)為上面代碼會(huì)輸出什么晓铆?

我們已經(jīng)知道勺良,除非有 case 執(zhí)行,select 語(yǔ)句就會(huì)一直阻塞著骄噪。在這里尚困,select 語(yǔ)句沒(méi)有任何 case,因此它會(huì)一直阻塞链蕊,導(dǎo)致死鎖事甜。該程序會(huì)觸發(fā) panic谬泌,輸出如下:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [select (no cases)]:  
main.main()  
    /tmp/sandbox299546399/main.go:4 +0x20
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市逻谦,隨后出現(xiàn)的幾起案子掌实,更是在濱河造成了極大的恐慌,老刑警劉巖邦马,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贱鼻,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡滋将,警方通過(guò)查閱死者的電腦和手機(jī)邻悬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)随闽,“玉大人父丰,你說(shuō)我怎么就攤上這事〕髁常” “怎么了础米?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)添诉。 經(jīng)常有香客問(wèn)我屁桑,道長(zhǎng),這世上最難降的妖魔是什么栏赴? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任蘑斧,我火速辦了婚禮,結(jié)果婚禮上须眷,老公的妹妹穿的比我還像新娘竖瘾。我一直安慰自己,他們只是感情好花颗,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布捕传。 她就那樣靜靜地躺著,像睡著了一般扩劝。 火紅的嫁衣襯著肌膚如雪庸论。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,718評(píng)論 1 305
  • 那天棒呛,我揣著相機(jī)與錄音聂示,去河邊找鬼。 笑死簇秒,一個(gè)胖子當(dāng)著我的面吹牛鱼喉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼扛禽,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼锋边!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起编曼,我...
    開(kāi)封第一講書(shū)人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤宠默,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后灵巧,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抹沪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年刻肄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片融欧。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡敏弃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出噪馏,到底是詐尸還是另有隱情麦到,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布欠肾,位于F島的核電站瓶颠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏刺桃。R本人自食惡果不足惜粹淋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瑟慈。 院中可真熱鬧桃移,春花似錦、人聲如沸葛碧。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)进泼。三九已至蔗衡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間缘琅,已是汗流浹背粘都。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留刷袍,地道東北人翩隧。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親堆生。 傳聞我的和親對(duì)象是個(gè)殘疾皇子专缠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容