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 就不會阻寒,而是進入處理就緒通道的分支流程。如果監(jiān)聽的通道有多個口讀或口寫的狀態(tài),則 select 隨利選取一個處理。
package main
func main() {
ch := make(chan int, 1)
go func(chan int) {
for {
select {
// 0或者1寫入是隨機的
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語句,可以實現(xiàn) main 主線程與 goroutine 線程之間的互動。
select使用時類似 switch-case 的用法,適用于處理多通道的場景,會通過類似 are-you-ready-polling 的機制來工作。
select {
case <-ch1 : // 檢測有沒有數(shù)據(jù)可讀
// 一旦成功讀取到數(shù)據(jù),則進行該case處理語句
case ch2 <- 1 : // 檢測有沒有數(shù)據(jù)可寫
// 一旦成功向ch2寫入數(shù)據(jù),則進行該case處理語句
default:
// 如果以上都沒有符合條件,那么進入default處理流程
}select 語句只能用于 channel 信道的IO操作,每個 case 都必須是一個信道。
如果不設(shè)置 default 條件,當(dāng)沒有IO操作發(fā)生時,select 語句就會一直阻塞。
如果有一個或多個IO操作發(fā)生時,Go運行時會隨機選擇一個 case 執(zhí)行,但此時將無法保證執(zhí)行順序。
對于 case 語句,如果存在信道值為 nil 的讀寫操作,則該分支將被忽略,可以理解為相當(dāng)于從select語句中刪除了這個case;
對于空的 select 語句,會引起死鎖;
對于在 for中的select語句,不能添加 default,否則會引起cpu占用過高的問題;
隨機性:多個 case 之間并非順序的,遵循「先到先執(zhí)行,同時到則隨機執(zhí)行」的原則。
一次性:和 switch-case 一樣,select-case也只會執(zhí)行一次,如果需要多次處理,需要在外層套一個循環(huán)。
default 不會阻塞,會一直執(zhí)行,當(dāng)與 for 循環(huán)組合使用時可能出現(xiàn)死循環(huán)。
1.2 阻塞與非阻塞 select
select 默認是阻塞的,當(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,可以實現(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語句是隨機執(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í)行后,會隨機打印 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)中運行,從而導(dǎo)致占滿整個CPU資源。
在 for{} 的 select 語句中,不建議使用 default 條件。
1.6 select語句的實際應(yīng)用
(1)、實現(xiàn) main主線程與 goroutine線程之間的交互、通信
package main
import (
"bufio"
"fmt"
"os"
)
// 通過控制臺輸入"bye",來控制main函數(shù)結(jié)束運行
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)、超時實現(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隨機執(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 隨機交替
以上就是Go語言并發(fā)之Select多路選擇操作符用法詳解的詳細內(nèi)容,更多關(guān)于Go Select的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Golang實現(xiàn)加權(quán)輪詢負載均衡算法
加權(quán)輪詢負載均衡算法是一種常見的負載均衡策略,本文主要介紹了Golang實現(xiàn)加權(quán)輪詢負載均衡算法,具有一定的參考價值,感興趣的可以了解一下2024-08-08
Golang?中判斷兩個結(jié)構(gòu)體相等的方法
這篇文章主要介紹了Golang?中如何判斷兩個結(jié)構(gòu)體相等,本文通過示例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-08-08
GoFrame?gredis緩存DoVar及Conn連接對象的自動序列化
這篇文章主要為大家介紹了GoFrame?gredis干貨DoVar?Conn連接對象自動序列化詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06
golang替換無法顯示的特殊字符(\u0000,?\000,?^@)
這篇文章主要介紹了golang替換無法顯示的特殊字符,包括的字符有\(zhòng)u0000,?\000,?^@等,下文詳細資料,需要的小伙伴可以參考一下2022-04-04
Go語言實現(xiàn)類似c++中的多態(tài)功能實例
Go本身不具有多態(tài)的特性,不能夠像Java、C++那樣編寫多態(tài)類、多態(tài)方法。但是,使用Go可以編寫具有多態(tài)功能的類綁定的方法。下面來一起看看吧2016-09-09
golang語言如何將interface轉(zhuǎn)為int, string,slice,struct等類型
這篇文章主要介紹了golang語言如何將interface轉(zhuǎn)為int, string,slice,struct等類型,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12

