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 默認(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,可以實現(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如何優(yōu)雅的關(guān)機或重啟操作示例
這篇文章主要為大家介紹了使用golang如何優(yōu)雅的關(guān)機或重啟操作示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪2022-04-04
詳解Go語言如何利用高階函數(shù)寫出優(yōu)雅的代碼
高階函數(shù)(Hiher-order?Function)定義為:滿足下列條件之一的函數(shù):接收一個或多個函數(shù)作為參數(shù);返回值是一個函數(shù)。本文為大家介紹了如何利用高階函數(shù)寫出優(yōu)雅的代碼,希望對大家有所幫助2023-01-01
Golang Cron 定時任務(wù)的實現(xiàn)示例
這篇文章主要介紹了Golang Cron 定時任務(wù)的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
Golang使用原生http實現(xiàn)中間件的代碼詳解
中間件(middleware):常被用來做認(rèn)證校驗、審計等,家常用的Iris、Gin等web框架,都包含了中間件邏輯,但有時我們引入該框架顯得較為繁重,本文將介紹通過golang原生http來實現(xiàn)中間件操作,需要的朋友可以參考下2024-05-05
Go語言題解LeetCode724尋找數(shù)組的中心下標(biāo)
這篇文章主要為大家介紹了Go語言題解LeetCode724尋找數(shù)組的中心下標(biāo),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12

