select是golang中的控制語句,和switch有點類似,但是使用場景和原理卻是完全不同,使用select配合channel可以實現協程之間的通信,也可以實現io層面的超時控制,也可以實現對于并發的控制
一、語法特點
1、select中的每個case都必須是一個通道
2、多個case中的通道,哪個符合就執行哪個通道,如果沒有沒有符合的case,要看是否有設置了default,如果有設置了default,就執行default中的邏輯,如果沒有設置default,就會一直阻塞至其中的case被執行
二、實現協程之間的通信
子協程向主協程通信
如下,main函數中定義了一個select,其中設置了一個通道的case,從ch通道中獲取數據。
main函數執行到select會阻塞,直到獲取通道的值,這時候通過子協程,對ch進行賦值,所以會先打印child,后打印select start
ch := make(chan int)
go func() {
fmt.Println("child")
time.Sleep(2 * time.Second)
ch <- 1
}()
select {
case res := <-ch:
fmt.Println("select start")
fmt.Println(res)
}
輸出如下
子協程向其他子協程通信
這邊定義了一個子協程,對childCh通道寫數據,另一個子協程在for無限循環中不斷的獲取childCh通道的值,如果有值,則打印得到的值
childCh := make(chan int)
go func() {
for {
select {
case res := <-childCh:
fmt.Println("child2")
fmt.Println(res)
}
}
}()
go func() {
fmt.Println("child")
time.Sleep(2 * time.Second)
childCh <- 1
}()
for {
time.Sleep(time.Second)
}
輸出如下:
多個子協程向主函數通信
如下,定義了兩個通道,select會在兩個通道隨機選擇,哪個通道先準備好了數據,就執行哪個通道
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
fmt.Println("child2")
ch2 <- 2
}()
go func() {
fmt.Println("child1")
ch1 <- 1
}()
for i := 0; i < 2; i++ {
select {
case res1 := <-ch1:
fmt.Println("ch1")
fmt.Println(res1)
case res2 := <-ch2:
fmt.Println("ch2")
fmt.Println(res2)
}
}
以下,主函數main中定義了兩個通道,select定義在for循環中,主函數不斷的從ch1和ch2通道中獲取數據,如果沒有數據的話,則執行default,兩個子協程則是不斷的往通道中分別寫入 from 1和from 2數據。
// 定義兩個通道
ch1 := make(chan string)
ch2 := make(chan string)
// 啟動兩個 goroutine,分別從兩個通道中獲取數據
go func() {
for {
ch1 <- "from 1"
}
}()
go func() {
for {
ch2 <- "from 2"
}
}()
// 使用 select 語句非阻塞地從兩個通道中獲取數據
for {
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
default:
// 如果兩個通道都沒有可用的數據,則執行這里的語句
fmt.Println("no message received")
}
}
三、實現io的超時控制
這邊使用的是select+channel+time.After實現了超時控制,這邊列的只是簡單的demo,io邏輯可以類似如下代碼,放在do函數中,當io請求時間超過了time.After定義的時間,就會輸出timeout
package main
import (
"fmt"
"time"
)
func main() {
// 創建一個可以取消的上下文
// 定義兩個通道
ch := make(chan string)
go do(ch)
select {
case res := <-ch:
fmt.Println("res")
fmt.Println(res)
case <-time.After(time.Second * 2):
fmt.Println("timeout")
}
}
func do(ch chan string) {
time.Sleep(3 * time.Second)
ch <- "done"
}
四、實現并發控制
適用場景:
比如某個邏輯getHttp中的邏輯只能允許一定的并發執行,當并發過高的時候會拖垮其中的服務或者外部服務時,我們就需要對并發進行控制。
這邊創建了一個最多只能存放2個值的semaphoreBig通道,當并發到來的時候,通過semaphoreBig 信號量進行控制只能同時處理2個并發,多余的并發會阻塞至有空余的信號量后才會執行。
package main
import (
"context"
"fmt"
"github.com/gin-gonic/gin"
)
var semaphoreBig = make(chan struct{}, 2) // 并發控制信號量
func main() {
r := gin.Default() //創建gin
r.Use(func(c *gin.Context) {
//處理請求
c.Next()
})
r.GET("/", index) //綁定路由
r.Run(":8001") //運行綁定端口
}
func index(c *gin.Context) {
Do(context.TODO())
}
func Do(ctx context.Context) {
select {
case <-ctx.Done():
fmt.Println("ctx done")
default:
var releaseSemaphore func()
semaphoreBig <- struct{}{}
releaseSemaphore = func() {
<-semaphoreBig // 釋放信號量
}
func() {
defer releaseSemaphore()
getHttp()
}()
}
}
// 執行相應的http邏輯
func getHttp() {
fmt.Println("http")
}