Go語言并發(fā)之Select多路選擇操作符用法詳解
1、Go語言并發(fā)之Select多路選擇操作符
select 是類 UNIX 系統(tǒng)提供的一個多路復(fù)用系統(tǒng) API,Go 語言借用多路復(fù)用的概念,提供了 select 關(guān)鍵字,用于多路監(jiān)聽多個通道。當(dāng)監(jiān)聽的通道沒有狀態(tài)是可讀或可寫的,select 是阻塞的;只要監(jiān)聽的通道中有一個狀態(tài)是可讀或可寫,則 select 就不會阻寒,而是進(jìn)入處理就緒通道的分支流程。如果監(jiān)聽的通道有多個口讀或口寫的狀態(tài),則 select 隨利選取一個處理。
package main func main() { ch := make(chan int, 1) go func(chan int) { for { select { // 0或者1寫入是隨機(jī)的 case ch <- 0: case ch <- 1: } } }(ch) for i := 0; i < 10; i++ { println(<-ch) } }
輸出
# 程序結(jié)果
1
1
1
1
0
0
0
0
1
1
1.1 多路選擇操作符 select
在golang語言中,select 語句就是用來監(jiān)聽和channel有關(guān)的IO操作,當(dāng)IO操作發(fā)生時,觸發(fā)相應(yīng)的case動作。
有了select語句,可以實(shí)現(xiàn) main 主線程與 goroutine 線程之間的互動。
select使用時類似 switch-case 的用法,適用于處理多通道的場景,會通過類似 are-you-ready-polling 的機(jī)制來工作。
select { case <-ch1 : // 檢測有沒有數(shù)據(jù)可讀 // 一旦成功讀取到數(shù)據(jù),則進(jìn)行該case處理語句 case ch2 <- 1 : // 檢測有沒有數(shù)據(jù)可寫 // 一旦成功向ch2寫入數(shù)據(jù),則進(jìn)行該case處理語句 default: // 如果以上都沒有符合條件,那么進(jìn)入default處理流程 }
select 語句只能用于 channel 信道的IO操作,每個 case 都必須是一個信道。
如果不設(shè)置 default 條件,當(dāng)沒有IO操作發(fā)生時,select 語句就會一直阻塞。
如果有一個或多個IO操作發(fā)生時,Go運(yùn)行時會隨機(jī)選擇一個 case 執(zhí)行,但此時將無法保證執(zhí)行順序。
對于 case 語句,如果存在信道值為 nil 的讀寫操作,則該分支將被忽略,可以理解為相當(dāng)于從select語句中刪除了這個case;
對于空的 select 語句,會引起死鎖;
對于在 for中的select語句,不能添加 default,否則會引起cpu占用過高的問題;
隨機(jī)性:多個 case 之間并非順序的,遵循「先到先執(zhí)行,同時到則隨機(jī)執(zhí)行」的原則。
一次性:和 switch-case 一樣,select-case也只會執(zhí)行一次,如果需要多次處理,需要在外層套一個循環(huán)。
default 不會阻塞,會一直執(zhí)行,當(dāng)與 for 循環(huán)組合使用時可能出現(xiàn)死循環(huán)。
1.2 阻塞與非阻塞 select
select 默認(rèn)是阻塞的,當(dāng)沒有 case 處于激活狀態(tài)時,會一直阻塞住,極端的甚至可以這樣用:
package main func main() { select { // 啥也不干,一直阻塞住 } }
執(zhí)行后,引發(fā)死鎖,打印如下:
# 輸出
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [select (no cases)]:
通過增加 default,可以實(shí)現(xiàn)非阻塞的 select:
select { case x, ok := <-ch1: ... case ch2 <- y: ... default: fmt.Println("default") }
1.3 多 case 與 default 執(zhí)行的順序
整體流程如圖所示:
1.4 多個IO操作發(fā)生時,case語句是隨機(jī)執(zhí)行的
package main import "fmt" func main() { // 創(chuàng)建一個長度帶緩沖的整型通道 ch1 := make(chan int, 1) // 向通道中寫入數(shù)據(jù) ch1 <- 1 ch2 := make(chan int, 1) ch2 <- 2 select { case <-ch1: fmt.Println("ch1 read") case <-ch2: fmt.Println("ch2 read") } }
多次執(zhí)行后,會隨機(jī)打印 ch1 read 或 ch2 read。
1.5 for中的select 引起CPU資源消耗過高
package main import ( "fmt" "time" ) func main() { quit := make(chan bool) go func() { for { select { case <-quit: fmt.Println("quit") // 使用 return 就會退出整個goroutine線程;如果使用 break,程序仍然在for循環(huán)中執(zhí)行 return default: fmt.Println("default") } } }() time.Sleep(3 * time.Second) quit <- true // 主線程在3秒后,向quit信道寫入數(shù)據(jù) time.Sleep(2 * time.Second) fmt.Println("main") }
輸出:
# 程序結(jié)果
default
default
default
default
default
default
default
......
......
default
default
default
default
default
quit
main
在 for{} 的 select 語句中使用了 default 后,線程就會無限執(zhí)行 default 條件,直到 quit 信道中讀到數(shù)據(jù),否則會一直在一個死循環(huán)中運(yùn)行,從而導(dǎo)致占滿整個CPU資源。
在 for{} 的 select 語句中,不建議使用 default 條件。
1.6 select語句的實(shí)際應(yīng)用
(1)、實(shí)現(xiàn) main主線程與 goroutine線程之間的交互、通信
package main import ( "bufio" "fmt" "os" ) // 通過控制臺輸入"bye",來控制main函數(shù)結(jié)束運(yùn)行 func main() { quit := make(chan bool) ch := make(chan string) go func() { for { select { case name := <-ch: fmt.Printf("from main msg: [%v]\n", name) if name == "bye" { quit <- true } else { quit <- false } } } }() for { // 控制臺輸入 fmt.Print("please input string: ") scanner := bufio.NewScanner(os.Stdin) scanner.Scan() ch <- scanner.Text() isOver := <-quit if isOver { break } } fmt.Println("main over") }
輸出:
please input string: from main msg: [ttttt]
please input string: from main msg: [qqqq]
please input string: from main msg: [wwww]
please input string: from main msg: [bye]
main over
(2)、超時實(shí)現(xiàn)
package main import ( "fmt" "time" ) func main() { quit := make(chan bool) ch := make(chan int) go func() { for { select { case num := <-ch: fmt.Println("num = ", num) case <-time.After(5 * time.Second): fmt.Println("超時") quit <- true } } }() for i := 0; i < 2; i++ { ch <- i time.Sleep(time.Second) } <-quit // 等待超時后, 結(jié)束 main主線程 fmt.Println("程序結(jié)束") }
輸出:
num = 0
num = 1
超時
程序結(jié)束
1.7 select使用的區(qū)別
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) s1 := <-output1 fmt.Println(s1) s2 := <-output2 fmt.Println(s2) }
程序結(jié)果
from server1
from server2
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) } }
程序結(jié)果
from server2
package main import "time" import ( "fmt" ) // select 管道參數(shù)并行 func server1(ch chan string) { time.Sleep(time.Second * 6) ch <- "response from server1" } func server2(ch chan string) { time.Sleep(time.Second * 3) ch <- "response from server2" } func main() { output1 := make(chan string) output2 := make(chan string) go server1(output1) go server2(output2) // 管道同時ready,select隨機(jī)執(zhí)行 // time.Sleep(time.Second) select { case s1 := <-output1: fmt.Println("s1:", s1) case s2 := <-output2: fmt.Println("s2:", s2) default: fmt.Println("run default") } }
程序結(jié)果
run default
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) } }
輸出
from server2 和 from server1 隨機(jī)交替
以上就是Go語言并發(fā)之Select多路選擇操作符用法詳解的詳細(xì)內(nèi)容,更多關(guān)于Go Select的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Golang實(shí)現(xiàn)加權(quán)輪詢負(fù)載均衡算法
加權(quán)輪詢負(fù)載均衡算法是一種常見的負(fù)載均衡策略,本文主要介紹了Golang實(shí)現(xiàn)加權(quán)輪詢負(fù)載均衡算法,具有一定的參考價值,感興趣的可以了解一下2024-08-08Golang?中判斷兩個結(jié)構(gòu)體相等的方法
這篇文章主要介紹了Golang?中如何判斷兩個結(jié)構(gòu)體相等,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-08-08GoFrame?gredis緩存DoVar及Conn連接對象的自動序列化
這篇文章主要為大家介紹了GoFrame?gredis干貨DoVar?Conn連接對象自動序列化詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06golang替換無法顯示的特殊字符(\u0000,?\000,?^@)
這篇文章主要介紹了golang替換無法顯示的特殊字符,包括的字符有\(zhòng)u0000,?\000,?^@等,下文詳細(xì)資料,需要的小伙伴可以參考一下2022-04-04Go語言實(shí)現(xiàn)類似c++中的多態(tài)功能實(shí)例
Go本身不具有多態(tài)的特性,不能夠像Java、C++那樣編寫多態(tài)類、多態(tài)方法。但是,使用Go可以編寫具有多態(tài)功能的類綁定的方法。下面來一起看看吧2016-09-09golang語言如何將interface轉(zhuǎn)為int, string,slice,struct等類型
這篇文章主要介紹了golang語言如何將interface轉(zhuǎn)為int, string,slice,struct等類型,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12