Golang關鍵字select的常用用法總結
一、Select解決什么問題

在Golang中,兩個協(xié)程之間通信Channel(圖一),在接受協(xié)程中通過代碼表示即為<ch;如果協(xié)程需要監(jiān)聽多個Channel,只要有其中一個滿足條件,就執(zhí)行相應的邏輯(圖二),這種select的應用場景之一,代碼如下:
func TestSelect(t *testing.T) {
ch1 := make(chan int, 1)
ch2 := make(chan int , 1)
select {
case <-ch1:
fmt.Println("ch1")
case <-ch2:
fmt.Println("ch2")
default:
}
}
上述代碼,創(chuàng)建兩個通道,通過select監(jiān)聽協(xié)程是否有數(shù)據(jù),如果有打印相應的值,如果沒有通過default結束程序運行;
二、Select常用用法
循環(huán)阻塞監(jiān)測
func TestLoopSelect(t *testing.T) {
ch1 := make(chan int, 1)
ch2 := make(chan int, 1)
go func() {
// 每隔1s發(fā)送一條消息到channel中
for range time.Tick(1 * time.Second) {
ch1 <- 1
}
}()
for {
select {
case <-ch1:
fmt.Println("ch1")
case <-ch2:
fmt.Println("ch2")
}
}
}
這種寫法特別常見,起一個協(xié)程,阻塞循環(huán)監(jiān)聽多個channel,如果有數(shù)據(jù)執(zhí)行對應的操作。比如說
// go-zero/core/discov/internal/registry.go
func (c *cluster) watchStream(cli EtcdClient, key string, rev int64) bool {
var rch clientv3.WatchChan
if rev != 0 {
rch = cli.Watch(clientv3.WithRequireLeader(c.context(cli)), makeKeyPrefix(key), clientv3.WithPrefix(),
clientv3.WithRev(rev+1))
} else {
rch = cli.Watch(clientv3.WithRequireLeader(c.context(cli)), makeKeyPrefix(key), clientv3.WithPrefix())
}
for {
select {
case wresp, ok := <-rch:
...
c.handleWatchEvents(key, wresp.Events)
case <-c.done:
return true
}
}
}
上述代碼,通過for + select阻塞循環(huán)監(jiān)測注冊中心數(shù)據(jù)是否有變換,有變化的話,針對變化類型,執(zhí)行對應邏輯;
非阻塞監(jiān)控
func TestSelect(t *testing.T) {
ch1 := make(chan int, 1)
ch2 := make(chan int , 1)
select {
case <-ch1:
fmt.Println("ch1")
case <-ch2:
fmt.Println("ch2")
default:
}
}
這段代碼的意思是,程序執(zhí)行到select,就檢查一下channel里面是否有數(shù)據(jù),有就處理,沒有就退出;在grpc-go中,
// grpc-go/clientconn.go
func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error) {
cc := &ClientConn{
target: target,
conns: make(map[*addrConn]struct{}),
dopts: defaultDialOptions(),
czData: new(channelzData),
}
defer func() {
select {
case <-ctx.Done():
switch {
case ctx.Err() == err:
conn = nil
case err == nil || !cc.dopts.returnLastError:
conn, err = nil, ctx.Err()
default:
conn, err = nil, fmt.Errorf("%v: %v", ctx.Err(), err)
}
default:
}
}()
if cc.dopts.scChan != nil {
// Blocking wait for the initial service config.
select {
case sc, ok := <-cc.dopts.scChan:
if ok {
cc.sc = &sc
cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{&sc})
}
case <-ctx.Done():
return nil, ctx.Err()
}
}
}
上述代碼中,有兩個地方用到select,一個地方是非阻塞,檢查contex是否被取消;另外一個是阻塞等待;
三、select原理

如果想了解select的實現(xiàn),可以閱讀runtime.selectgo代碼。它主要包含三部分:
首先,檢查一下是否有準備就緒的channel(多個channel就緒,隨機選擇一個),如果有,就執(zhí)行;
其次,將當前goroutine包裝成sudog,掛載到對應的channel上;
最后,如果channel中數(shù)據(jù)準備就緒,喚醒該協(xié)程繼續(xù)執(zhí)行第一步邏輯;
【注】源碼細節(jié),感興趣并且有需求可以深入了解,但是不要陷入源碼的怪圈中;
總結
本文主要講述下面三部分內容:
- 從Go源碼開發(fā)者的角度考慮,為什么需要select?
- 介紹了select常用的兩種寫法,一種是非阻塞的,一種是阻塞的,以及開源項目如何使用它們;
- 介紹了select的基本實現(xiàn);
以上就是Golang關鍵字select的常用用法總結的詳細內容,更多關于go select的資料請關注腳本之家其它相關文章!
相關文章
golang?使用sort.slice包實現(xiàn)對象list排序
這篇文章主要介紹了golang?使用sort.slice包實現(xiàn)對象list排序,對比sort跟slice兩種排序的使用方式區(qū)別展開內容,需要的小伙伴可以參考一下2022-03-03
go語言題解LeetCode1128等價多米諾骨牌對的數(shù)量
這篇文章主要為大家介紹了go語言題解LeetCode1128等價多米諾骨牌對的數(shù)量示例詳解,2022-12-12
關于golang中map使用的幾點注意事項總結(強烈推薦!)
map是一種無序的基于key-value的數(shù)據(jù)結構,Go語言中的map是引用類型,必須初始化才能使用,下面這篇文章主要給大家介紹了關于golang中map使用的幾點注意事項,需要的朋友可以參考下2023-01-01
golang實現(xiàn)nacos獲取配置和服務注冊-支持集群詳解
文章介紹了如何在Go語言中使用Nacos獲取配置和服務注冊,支持集群初始化,客戶端結構體中的IpAddresses可以配置多個地址,新客戶端支持集群配置,文章還討論了如何將Nacos獲取到的配置與項目中的Viper無縫對接,以便進行Viper的正常操作2025-11-11
Go語言實現(xiàn)的排列組合問題實例(n個數(shù)中取m個)
這篇文章主要介紹了Go語言實現(xiàn)的排列組合問題,結合實例形式分析了Go語言實現(xiàn)排列組合數(shù)學運算的原理與具體操作技巧,需要的朋友可以參考下2017-02-02

