最近公司工作有點多,Golang的select進階就這樣被拖沓啦,今天堅持把時間擠一擠,把吹的牛皮補上。
前一篇文章《Golang併發模型:輕鬆入門select》介紹了select的作用和它的基本用法,這次介紹它的3個進階特性。
-
nil
的通道永遠阻塞 - 如何跳出
for-select
-
select{}
阻塞
nil
的通道永遠阻塞
當case
上讀一個通道時,如果這個通道是nil
,則該case
永遠阻塞。這個功能有1個妙用,select
通常處理的是多個通道,當某個讀通道關閉了,但不想select
再繼續關注此case
,繼續處理其他case
,把該通道設置爲nil
即可。
下面是一個合併程序等待兩個輸入通道都關閉後才退出的例子,就使用了這個特性。
func combine(inCh1, inCh2 <-chan int) <-chan int {
// 輸出通道
out := make(chan int)
// 啓動協程合併數據
go func() {
defer close(out)
for {
select {
case x, open := <-inCh1:
if !open {
inCh1 = nil
continue
}
out<-x
case x, open := <-inCh2:
if !open {
inCh2 = nil
continue
}
out<-x
}
// 當ch1和ch2都關閉是才退出
if inCh1 == nil && inCh2 == nil {
break
}
}
}()
return out
}
如何跳出for-select
break
在select
內的並不能跳出for-select
循環。看下面的例子,consume
函數從通道inCh
不停讀數據,期待在inCh
關閉後退出for-select
循環,但結果是永遠沒有退出。
func consume(inCh <-chan int) {
i := 0
for {
fmt.Printf("for: %d\n", i)
select {
case x, open := <-inCh:
if !open {
break
}
fmt.Printf("read: %d\n", x)
}
i++
}
fmt.Println("combine-routine exit")
}
運行結果:
➜ go run x.go
for: 0
read: 0
for: 1
read: 1
for: 2
read: 2
for: 3
gen exit
for: 4
for: 5
for: 6
for: 7
for: 8
... // never stop
既然break
不能跳出for-select
,那怎麼辦呢?給你3個錦囊:
- 在滿足條件的
case
內,使用return
,如果有結尾工作,嘗試交給defer
。 - 在
select
外for
內使用break
挑出循環,如combine
函數。 - 使用
goto
。
select{}
永遠阻塞
select{}
的效果等價於創建了1個通道,直接從通道讀數據:
ch := make(chan int)
<-ch
但是,這個寫起來多麻煩啊!沒select{}
簡潔啊。
但是,永遠阻塞能有什麼用呢!?
當你開發一個併發程序的時候,main
函數千萬不能在子協程幹完活前退出啊,不然所有的協程都被迫退出了,還怎麼提供服務呢?
比如,寫了個Web服務程序,端口監聽、後端處理等等都在子協程跑起來了,main
函數這時候能退出嗎?
select應用場景
最後,介紹下我常用的select
場景:
- 無阻塞的讀、寫通道。即使通道是帶緩存的,也是存在阻塞的情況,使用select可以完美的解決阻塞讀寫,這篇文章我之前發在了個人博客,後面給大家介紹下。
- 給某個請求/處理/操作,設置超時時間,一旦超時時間內無法完成,則停止處理。
-
select
本色:多通道處理
併發系列文章推薦
- 如果這篇文章對你有幫助,請點個贊/喜歡,鼓勵我持續分享,感謝。
- 我的文章列表,點此可查看
- 如果喜歡本文,隨意轉載,但請保留此原文鏈接。