Go 開發常用操作技巧 --channel

在 Go 語言中,提倡使用通信來共享內存,而不是通過共享內存來通信,這裏的通信就是通過 channel 發送接收消息的方式進行數據傳遞,而不是通過修改同一個變量。所以在數據流動、傳遞的場景中要考慮優先使用 channel,它是併發安全的,性能也不錯。

channel 聲明

ch := make(chan string)

在聲明 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。

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