Go語言并發(fā)之Select多路選擇操作符用法詳解
1、Go語言并發(fā)之Select多路選擇操作符
select 是類 UNIX 系統(tǒng)提供的一個(gè)多路復(fù)用系統(tǒng) API,Go 語言借用多路復(fù)用的概念,提供了 select 關(guān)鍵字,用于多路監(jiān)聽多個(gè)通道。當(dāng)監(jiān)聽的通道沒有狀態(tài)是可讀或可寫的,select 是阻塞的;只要監(jiān)聽的通道中有一個(gè)狀態(tài)是可讀或可寫,則 select 就不會(huì)阻寒,而是進(jìn)入處理就緒通道的分支流程。如果監(jiān)聽的通道有多個(gè)口讀或口寫的狀態(tài),則 select 隨利選取一個(gè)處理。
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ā)生時(shí),觸發(fā)相應(yīng)的case動(dòng)作。
有了select語句,可以實(shí)現(xiàn) main 主線程與 goroutine 線程之間的互動(dòng)。
select使用時(shí)類似 switch-case 的用法,適用于處理多通道的場景,會(huì)通過類似 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操作,每個(gè) case 都必須是一個(gè)信道。
如果不設(shè)置 default 條件,當(dāng)沒有IO操作發(fā)生時(shí),select 語句就會(huì)一直阻塞。
如果有一個(gè)或多個(gè)IO操作發(fā)生時(shí),Go運(yùn)行時(shí)會(huì)隨機(jī)選擇一個(gè) case 執(zhí)行,但此時(shí)將無法保證執(zhí)行順序。
對于 case 語句,如果存在信道值為 nil 的讀寫操作,則該分支將被忽略,可以理解為相當(dāng)于從select語句中刪除了這個(gè)case;
對于空的 select 語句,會(huì)引起死鎖;
對于在 for中的select語句,不能添加 default,否則會(huì)引起cpu占用過高的問題;
隨機(jī)性:多個(gè) case 之間并非順序的,遵循「先到先執(zhí)行,同時(shí)到則隨機(jī)執(zhí)行」的原則。
一次性:和 switch-case 一樣,select-case也只會(huì)執(zhí)行一次,如果需要多次處理,需要在外層套一個(gè)循環(huán)。
default 不會(huì)阻塞,會(huì)一直執(zhí)行,當(dāng)與 for 循環(huán)組合使用時(shí)可能出現(xiàn)死循環(huán)。
1.2 阻塞與非阻塞 select
select 默認(rèn)是阻塞的,當(dāng)沒有 case 處于激活狀態(tài)時(shí),會(huì)一直阻塞住,極端的甚至可以這樣用:
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 多個(gè)IO操作發(fā)生時(shí),case語句是隨機(jī)執(zhí)行的
package main
import "fmt"
func main() {
// 創(chuàng)建一個(gè)長度帶緩沖的整型通道
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í)行后,會(huì)隨機(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 就會(huì)退出整個(gè)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 后,線程就會(huì)無限執(zhí)行 default 條件,直到 quit 信道中讀到數(shù)據(jù),否則會(huì)一直在一個(gè)死循環(huán)中運(yùn)行,從而導(dǎo)致占滿整個(gè)CPU資源。
在 for{} 的 select 語句中,不建議使用 default 條件。
1.6 select語句的實(shí)際應(yīng)用
(1)、實(shí)現(xiàn) main主線程與 goroutine線程之間的交互、通信
package main
import (
"bufio"
"fmt"
"os"
)
// 通過控制臺(tái)輸入"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 {
// 控制臺(tái)輸入
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í)實(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("超時(shí)")
quit <- true
}
}
}()
for i := 0; i < 2; i++ {
ch <- i
time.Sleep(time.Second)
}
<-quit // 等待超時(shí)后, 結(jié)束 main主線程
fmt.Println("程序結(jié)束")
}輸出:
num = 0
num = 1
超時(shí)
程序結(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)
// 管道同時(shí)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)文章
Go程序的init函數(shù)在什么時(shí)候執(zhí)行
在Go語言中,init?函數(shù)是一個(gè)特殊的函數(shù),它用于執(zhí)行程序的初始化任務(wù),本文主要介紹了Go程序的init函數(shù)在什么時(shí)候執(zhí)行,感興趣的可以了解一下2023-10-10
Golang實(shí)現(xiàn)加權(quán)輪詢負(fù)載均衡算法
加權(quán)輪詢負(fù)載均衡算法是一種常見的負(fù)載均衡策略,本文主要介紹了Golang實(shí)現(xiàn)加權(quán)輪詢負(fù)載均衡算法,具有一定的參考價(jià)值,感興趣的可以了解一下2024-08-08
Golang?中判斷兩個(gè)結(jié)構(gòu)體相等的方法
這篇文章主要介紹了Golang?中如何判斷兩個(gè)結(jié)構(gòu)體相等,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08
GoFrame?gredis緩存DoVar及Conn連接對象的自動(dòng)序列化
這篇文章主要為大家介紹了GoFrame?gredis干貨DoVar?Conn連接對象自動(dòng)序列化詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
golang替換無法顯示的特殊字符(\u0000,?\000,?^@)
這篇文章主要介紹了golang替換無法顯示的特殊字符,包括的字符有\(zhòng)u0000,?\000,?^@等,下文詳細(xì)資料,需要的小伙伴可以參考一下2022-04-04
Go語言實(shí)現(xiàn)類似c++中的多態(tài)功能實(shí)例
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等類型,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12

