之前的文章都提到過,Golang的併發模型都來自生活,select也不例外。舉個例子:我們都知道一句話,“吃飯睡覺打豆豆”,這一句話裏包含了3件事:
- 媽媽喊你吃飯,你去吃飯。
- 時間到了,要睡覺。
- 沒事做,打豆豆。
在Golang裏,select就是幹這個事的:到吃飯了去吃飯,該睡覺了就睡覺,沒事幹就打豆豆。
結束髮散,我們看下select的功能,以及它能做啥。
select功能
在多個通道上進行讀或寫操作,讓函數可以處理多個事情,但1次只處理1個。以下特性也都必須熟記於心:
- 每次執行select,都會只執行其中1個case或者執行default語句。
- 當沒有case或者default可以執行時,select則阻塞,等待直到有1個case可以執行。
- 當有多個case可以執行時,則隨機選擇1個case執行。
-
case
後面跟的必須是讀或者寫通道的操作,否則編譯出錯。
select長下面這個樣子,由select
和case
組成,default
不是必須的,如果沒其他事可做,可以省略default
。
func main() {
readCh := make(chan int, 1)
writeCh := make(chan int, 1)
y := 1
select {
case x := <-readCh:
fmt.Printf("Read %d\n", x)
case writeCh <- y:
fmt.Printf("Write %d\n", y)
default:
fmt.Println("Do what you want")
}
}
我們創建了readCh
和writeCh
2個通道:
-
readCh
中沒有數據,所以case x := <-readCh
讀不到數據,所以這個case不能執行。 -
writeCh
是帶緩衝區的通道,它裏面是空的,可以寫入1個數據,所以case writeCh <- y
可以執行。 - 有
case
可以執行,所以default
不會執行。
這個測試的結果是
$ go run example.go
Write 1
用打豆豆實踐select
來,我們看看select怎麼實現打豆豆:eat()
函數會啓動1個協程,該協程先睡幾秒,事件不定,然後喊你吃飯,main()
函數中的sleep
是個定時器,每3秒喊你吃1次飯,select
則處理3種情況:
- 從
eatCh
中讀到數據,代表有人喊我吃飯,我要吃飯了。 - 從
sleep.C
中讀到數據,代表鬧鐘時間到了,我要睡覺。 -
default
是,沒人喊我吃飯,也不到時間睡覺,我就打豆豆。
import (
"fmt"
"time"
"math/rand"
)
func eat() chan string {
out := make(chan string)
go func (){
rand.Seed(time.Now().UnixNano())
time.Sleep(time.Duration(rand.Intn(5)) * time.Second)
out <- "Mom call you eating"
close(out)
}()
return out
}
func main() {
eatCh := eat()
sleep := time.NewTimer(time.Second * 3)
select {
case s := <-eatCh:
fmt.Println(s)
case <- sleep.C:
fmt.Println("Time to sleep")
default:
fmt.Println("Beat DouDou")
}
}
由於前2個case都要等待一會,所以都不能執行,所以執行default
,運行結果一直是打豆豆:
$ go run x.go
Beat DouDou
現在我們不打豆豆了,你把default
和下面的打印註釋掉,多運行幾次,有時候會吃飯,有時候會睡覺,比如這樣:
$ go run x.go
Mom call you eating
$ go run x.go
Time to sleep
$ go run x.go
Time to sleep
select很簡單但功能很強大,它讓golang的併發功能變的更強大。這篇文章寫的囉嗦了點,重點是爲下一篇文章做鋪墊,下一篇我們將介紹下select的高級用法。
select的應用場景很多,讓我總結一下,放在下一篇文章中吧。
併發系列文章推薦
- 如果這篇文章對你有幫助,請點個贊/喜歡,鼓勵我持續分享,感謝。
- 我的文章列表,點此可查看
- 如果喜歡本文,隨意轉載,但請保留此原文鏈接。