Go 開發常用操作技巧 --channel
在 Go 語言中,提倡使用通信來共享內存,而不是通過共享內存來通信,這裏的通信就是通過 channel 發送接收消息的方式進行數據傳遞,而不是通過修改同一個變量。所以在數據流動、傳遞的場景中要考慮優先使用 channel,它是併發安全的,性能也不錯。
channel 聲明
ch := make(chan string)
-
使用 make 函數
-
chan 是關鍵字,表示 channel 類型,chan 是一個集合類型
-
string 表示 channel 裏存放數據的類型
在聲明 channel 時,需要指定被共享的數據類型,其中可以通過 channel 共享的類型有 內置類型、命名類型、結構類型和引用類型的值或指針。
channel 使用
chan 只有發送和接收兩種操作:
發送:<-chan
// 向 chan 內發送數據 接收:chan->
// 從 chan 中獲取數據
channel 分爲有緩衝和無緩衝兩種,下面來分別介紹下。
無緩衝 channel
無緩衝 channel,通道的容量是 0,它不能存儲數據,只是起到了傳輸的作用,所以無緩衝 channel 的發送和接收操作是同時進行的。
示例:
package main
import (
"fmt"
)
func main() {
ch := make(chan string)
go func(){
fmt.Println("微客鳥窩")
ch <- "執行完畢"
}()
fmt.Println("我是無塵啊")
value := <-ch
fmt.Println("獲取的chan的值爲:",value)
}
運行結果:
我是無塵啊
微客鳥窩
獲取的chan的值爲: 執行完畢
有緩衝 channel
在聲明的時候,我們可以傳入第二個參數,即「channel 容量大小」,這樣就是創建了一個有緩衝 channel。
-
有緩衝 channel 內部有一個隊列
-
發送操作是向隊列尾部追加元素,如果隊列滿了,則阻塞等待,直到接收操作從隊列中取走元素。
-
接收操作是從隊列頭部取走元素,如果隊列爲空,則阻塞等待,直到發送操作向隊列追加了元素。
-
可以通過內置函數
cap()
來獲取 channel 的容量,通過內置函數len()
獲取 channel 中元素個數。
ch := make(chan int,3)
ch <- 1
ch <- 2
fmt.Println("容量爲",cap(ch),"元素個數爲:",len(ch))
//打印結果:容量爲 3 元素個數爲:2
關閉 channel
使用內置函數 close(ch)
對端可以判斷 channel 是否關閉
if num,ok := <- ch;ok == true{
//如果對端已經關閉,ok-->false
//如果對端沒有關閉,ok-->true
}
也可以使用 range 替代 ok
//當channel關閉時,range方式會將裏面剩餘的數據全部讀取完成,再退出
//ch不能替換爲<-ch ;
for num := range ch{
fmt.Println("讀到數據:",num)
}
channel 關閉了就不能再向其發送數據了,否則會引起 panic 異常。 可以從關閉了的 channel 中接收數據,如果沒數據,則接收到的是元素類型的零值。
單向 channel
只能發送或者只能接收的 channel 爲單向 channel。
send := make(ch<- int) //只能發送數據給channel
receive := make(<-ch int) //只能從channel中接收數據
示例:
package main
import (
"fmt"
)
//只能發送通道
func send(s chan<- string){
s <- "微客鳥窩"
}
//只能接收通道
func receive(r <-chan string){
str := <-r
fmt.Println("str:",str)
}
func main() {
//創建一個雙向通道
ch := make(chan string)
go send(ch)
receive(ch)
}
//運行結果: str: 微客鳥窩
實戰技巧
控制 goroutine 併發的數量
func main(){
m1 := make(chan bool)
go func() {
for{
select {
case <-m1:
fmt.Println("close")
return
case <-time.After(time.Millisecond*10000):
fmt.Println("-----")
}
}
}()
// 控制 goroutine 併發的數量
limit := make(chan bool,3)
wg := sync.WaitGroup{}
for i :=0;i<10;i++{
limit <- true
wg.Add(1)
go func(i int) {
defer func() {
<- limit
wg.Done()
}()
//HandleLogic()
fmt.Println(i)
}(i)
}
wg.Wait()
m1 <- true
close(m1)
}
for select 無限循環模式
一般是和 channel 組合完成任務,格式爲:
for { //for 無限循環,或者使用 for range 循環
select {
//通過 channel 控制
case <-done:
return
default:
//執行具體的任務
}
}
這種是 for + select
多路複用的併發模式,哪個 case 滿足條件就執行對應的分支,直到有滿足退出的條件,纔會退出循環。沒有退出條件滿足時,則會一直執行 default 分支。
select timeout 模式
假如一個請求需要訪問服務器獲取數據,但是可能因爲網絡問題而遲遲獲取不到響應,這時候就需要設置一個超時時間:
package main
import (
"fmt"
"time"
)
func main() {
result := make(chan string)
timeout := time.After(3 * time.Second) //3秒後超時
go func() {
//模擬網絡訪問
time.Sleep(5 * time.Second)
result <- "服務端結果"
}()
for {
select {
case v := <-result:
fmt.Println(v)
case <-timeout:
fmt.Println("網絡訪問超時了")
return
default:
fmt.Println("等待...")
time.Sleep(1 * time.Second)
}
}
}
運行結果:
等待...
等待...
等待...
網絡訪問超時了
select timeout 模式核心是通過 time.After 函數設置的超時時間,防止因爲異常造成 select 語句無限等待。
注意:不要寫成這樣 case <- time.After(time.Second)
, 這樣是本次監聽動作的超時時間,意思就說,只有在本次 select 操作中會有效,再次 select 又會重新開始計時,但是有 default ,每次都會走到 default,那 case 超時操作,肯定執行不到了。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/157gT-9Xd8puDVRhs4EUQA