Go語(yǔ)言中select使用詳解
什么是 select?
select
是 Go 中用于 多路 channel 操作 的控制結(jié)構(gòu),它可以監(jiān)聽(tīng)多個(gè) channel 的發(fā)送與接收操作,當(dāng)其中某一個(gè)可以進(jìn)行時(shí)就執(zhí)行對(duì)應(yīng)的語(yǔ)句,從而實(shí)現(xiàn)非阻塞并發(fā)通信。
基本語(yǔ)法
select { case val := <-ch1: // ch1 可讀時(shí)執(zhí)行 case ch2 <- 100: // ch2 可寫時(shí)執(zhí)行 default: // 所有 channel 都阻塞時(shí)執(zhí)行(可選) }
每個(gè)
case
必須是 發(fā)送(ch <- val)或接收(val := <-ch)只會(huì)執(zhí)行一個(gè)可操作的
case
(如果多個(gè)都可以隨機(jī)挑一個(gè))如果都阻塞,且沒(méi)有
default
,select
會(huì)阻塞等待如果包含
default
,它會(huì)立即執(zhí)行,哪怕其他case
可能之后才可操作
使用場(chǎng)景舉例
1. 監(jiān)聽(tīng)多個(gè) channel 的數(shù)據(jù)
select { case msg1 := <-ch1: fmt.Println("收到 ch1:", msg1) case msg2 := <-ch2: fmt.Println("收到 ch2:", msg2) }
2. 實(shí)現(xiàn)超時(shí)機(jī)制
select { case msg := <-ch: fmt.Println("收到:", msg) case <-time.After(2 * time.Second): fmt.Println("超時(shí)") }
time.After
返回一個(gè) channel,在指定時(shí)間后變?yōu)榭勺x,實(shí)現(xiàn)優(yōu)雅的 timeout。
3. 非阻塞發(fā)送或接收(default 分支)
select { case ch <- data: fmt.Println("發(fā)送成功") default: fmt.Println("channel 滿,放棄發(fā)送") }
4. 檢測(cè)通道是否關(guān)閉
select { case v, ok := <-ch: if !ok { fmt.Println("通道關(guān)閉") } else { fmt.Println("收到:", v) } }
select 的行為特點(diǎn)
行為 | 描述 |
---|---|
隨機(jī)調(diào)度 | 多個(gè) case 同時(shí)可用時(shí)隨機(jī)選擇一個(gè)執(zhí)行(防止饑餓) |
阻塞等待 | 所有 case 阻塞時(shí),select 自身也阻塞 |
default 分支 | 所有 case 阻塞時(shí)立即執(zhí)行,避免阻塞 |
只選一個(gè) | 同時(shí)滿足多個(gè)時(shí)只執(zhí)行其中一個(gè) |
與 goroutine 配合:生產(chǎn)者/消費(fèi)者模型
func producer(ch chan int) { for i := 0; i < 5; i++ { ch <- i } close(ch) } ? func consumer(ch chan int, done chan struct{}) { for { select { case val, ok := <-ch: if !ok { done <- struct{}{} return } fmt.Println("消費(fèi):", val) } } }
select + for:常見(jiàn)循環(huán)寫法
for { select { case msg := <-ch: fmt.Println("收到:", msg) case <-time.After(5 * time.Second): fmt.Println("超時(shí)退出") return } }
select 常見(jiàn)陷阱
陷阱 | 描述 |
---|---|
忘記 default 導(dǎo)致阻塞 | 如果所有 case 阻塞,select 也阻塞 |
無(wú)限阻塞 | select 中所有 channel 永遠(yuǎn)不會(huì)可用 |
channel 已關(guān)閉 | 向已關(guān)閉通道寫入會(huì) panic,應(yīng)特別小心 |
超時(shí)誤用 | 使用 time.After() 時(shí),不要在循環(huán)里頻繁創(chuàng)建新 channel,否則內(nèi)存泄漏 |
?? 解決建議:使用 time.NewTimer
,并復(fù)用 timer。
實(shí)戰(zhàn)建議
建議 | 原因 |
---|---|
用 select 實(shí)現(xiàn)超時(shí)控制 | 比較優(yōu)雅且非阻塞 |
select + default 實(shí)現(xiàn)非阻塞通信 | 避免 goroutine 卡死 |
用 select + context.Done() 控制退出 | 在大型系統(tǒng)中更適合 |
例子:context 控制退出(推薦生產(chǎn)使用)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() ? ch := make(chan int) ? go func() { time.Sleep(2 * time.Second) ch <- 42 }() ? select { case <-ctx.Done(): fmt.Println("操作取消/超時(shí):", ctx.Err()) case val := <-ch: fmt.Println("接收到數(shù)據(jù):", val) }
總結(jié)
維度 | 說(shuō)明 |
---|---|
功能 | 實(shí)現(xiàn) channel 的多路復(fù)用監(jiān)聽(tīng) |
優(yōu)點(diǎn) | 非阻塞、高效、優(yōu)雅處理通信控制 |
結(jié)合 | time.After、context、default 最佳組合 |
場(chǎng)景 | goroutine 退出、任務(wù)超時(shí)、并發(fā)協(xié)程間通信控制等 |
到此這篇關(guān)于Go語(yǔ)言中select使用的文章就介紹到這了,更多相關(guān)Go select詳解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang常用庫(kù)之配置文件解析庫(kù)-viper使用詳解
viper 配置管理解析庫(kù),是由大神 Steve Francia 開(kāi)發(fā),他在google領(lǐng)導(dǎo)著 golang 的產(chǎn)品開(kāi)發(fā),他也是 gohugo.io 的創(chuàng)始人之一,命令行解析庫(kù) cobra 開(kāi)發(fā)者,這篇文章主要介紹了golang常用庫(kù)之配置文件解析庫(kù)-viper使用詳解,需要的朋友可以參考下2020-10-10Go語(yǔ)言如何實(shí)現(xiàn)線程安全的Map
Go語(yǔ)言內(nèi)置的map雖然高效,但并不是線程安全的,若在多線程環(huán)境中直接操作map,可能會(huì)引發(fā)并發(fā)寫入的錯(cuò)誤,下面我們就來(lái)看看如何實(shí)現(xiàn)線程安全的Map吧2024-11-11Go語(yǔ)言學(xué)習(xí)網(wǎng)絡(luò)編程與Http教程示例
這篇文章主要為大家介紹了Go語(yǔ)言學(xué)習(xí)網(wǎng)絡(luò)編程與Http教程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03Go中使用操作符進(jìn)行數(shù)學(xué)運(yùn)算的示例代碼
在編程中有效地執(zhí)行數(shù)學(xué)運(yùn)算是一項(xiàng)需要開(kāi)發(fā)的重要技能,本文主要介紹了Go中使用操作符進(jìn)行數(shù)學(xué)運(yùn)算的示例代碼,具有一定的參考價(jià)值,感興趣的可以了解一下2023-10-10Go語(yǔ)言Http?Server框架實(shí)現(xiàn)一個(gè)簡(jiǎn)單的httpServer
這篇文章主要為大家介紹了Go語(yǔ)言Http?Server框架實(shí)現(xiàn)一個(gè)簡(jiǎn)單的httpServer抽象,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04Golang WaitGroup實(shí)現(xiàn)原理解析
WaitGroup是Golang并發(fā)的兩種方式之一,一個(gè)是Channel,另一個(gè)是WaitGroup,下面這篇文章主要給大家介紹了關(guān)于golang基礎(chǔ)之waitgroup用法以及使用要點(diǎn)的相關(guān)資料,需要的朋友可以參考下2023-02-02解讀rand.Seed(time.Now().UnixNano())的作用及說(shuō)明
這篇文章主要介紹了關(guān)于rand.Seed(time.Now().UnixNano())的作用及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。2023-03-03