Golang | 簡介channel常見用法,完成goroutin通訊

TechFlow2019發表於2020-08-30

今天是golang專題的第14篇文章,大家可以點選上方的專輯回顧之前的內容。

今天我們來看看golang當中另一個很重要的概念——通道。我們之前介紹goroutine的時候曾經提過一個問題,當我們啟動了多個goroutine之後,我們怎麼樣讓goroutine之間保持通訊呢?

要回答這個問題就需要用到通道。

channel

通道的英文是channel,在golang當中的關鍵字是chan。它的用途是用來在goroutine之間傳輸資料,這裡你可能要問了,為什麼一定得是goroutine之間傳輸資料呢,函式之間傳遞不行嗎?

因為正常的傳輸資料直接以引數的形式傳遞就可以了,只有在併發場景當中,多個執行緒彼此隔離的情況下,才需要一個特殊的結構傳輸資料。

Chan看起來比較怪,在其他語言當中基本沒有出現過,但是它的原理和使用都非常簡單。

我們先來看它的使用,首先是定義一個chan,還是老規矩,通過make關鍵字建立。我們之前也提過,golang當中的一個設計原則就是能省則省,能簡單則簡單。從這個make關鍵字就看得出來,它可以建立的東西太多了,既可以建立一個切片,也可以建立map,還可以建立通道。

所以當我們要建立一個chan的時候,可以通過make實現。

Ch := make(chan int)

我們在chan後面跟上一個型別,表示這個通道傳輸的資料型別。如果你想要傳輸任何型別呢,那可以用我們之前說過的interface{}。

Chan建立了之後,我們想要從其中獲取資料或者是把資料放入其中也非常簡單,簡單到都沒有api,直接用形象的傳輸語句就可以了。

比如我們現在有一個chan是ch,我們想要放入資料,我們可以這樣ch <- a。我們想要從ch當中獲取資料,我們可以v := <- ch。

我們用箭頭表示資料的流動,是不是很形象很直觀呢?

阻塞

但是還沒完,chan有一個很關鍵的點在於,chan的使用是阻塞的。也就是說下游從chan當中拿走一個資料我們才可以傳入一個資料。否則的話,傳輸資料的程式碼就會一直等待chan清空。

同樣,如果我們定義了一個從chan當中讀取資料的語句,假如當前的chan是空的話,那麼它也會一直阻塞等待,直到chan當中有資料了為止。

所以我們就知道了,chan的使用場景當中需要一個生產方,也需要一個消費方。我們來看一個golang官方的一個例子:

package main

import "fmt"

func sum(s []int, c chan int) {
 sum := 0
 for _, v := range s {
  sum += v
 }
 c <- sum // 將和送入 c
}

func main() {
 s := []int{728-940}

 c := make(chan int)
 go sum(s[:len(s)/2], c)
 go sum(s[len(s)/2:], c)
 x, y := <-c, <-c // 從 c 中接收

 fmt.Println(x, y, x+y)
}

我們啟動了兩個goroutine去對陣列進行求和並進行返回,goroutine生產的資料是沒辦法直接return的,所以只能通過chan的形式傳輸出來。chan傳輸出來需要下游消費,所以上面兩個goroutine的資料會傳輸到x, y: <-c, <-c 這一句語句當中。

前面說過了,chan的傳輸是阻塞的,所以這一句語句會一直等待,直到上面兩個goroutine都計算完成了為止。

如果你看的有些發矇,覺得好似有些理解了又好似沒有的話,那麼很簡單的一個辦法是在理解的時候把這個使用場景做一個變幻。把chan的使用場景想象成我們之前介紹過的生產者消費者設計模式,chan在其中扮演的角色其實就是佇列。

生產者往佇列當中傳輸資料,消費者進行消費,唯一不同的是這個佇列的容量是1,必須要生產和消費端都準備就緒了才會進行資料傳輸。

chan的緩衝

前文說了,chan的容量只有1,只有消費端和生產端都就緒的時候才可以傳輸資料。我們也可以給chan加上緩衝,如果消費端來不及把所有的資料都消費完,允許生產端先把資料暫時存在chan當中,先不發生阻塞,只有在chan滿了之後才會阻塞。

用法也很簡單,我們在通過make建立chan的時候多加上一個參數列示容量即可,和我們之前建立切片的道理很類似。

Ch := make(chan int100)

比如這樣,我們就建立了一個緩衝區為100的通道。

但多說一句,其實這種情況不太常用,原因也很簡單。因為上下游的消費情況是統一的,如果生產者生產的速度過快,而消費端跟不上的話,即使把它先暫存在緩衝區當中也沒什麼用,早晚還是會要阻塞的。

close

當我們對通道使用結束之後,可以通過close語句將它關閉。

Close這個操作只能在生產端進行,消費端如果close通道會引發一個panic。我們在從chan接收資料的時候,可以加上一個引數判斷通道是否關閉。

v, ok := <- ch
if !ok {
 return
}

這樣我們就可以判斷chan關閉的時間了。

今天的文章到這裡就結束了,如果喜歡本文的話,請來一波素質三連,給我一點支援吧(關注、轉發、點贊)。

相關閱讀

Python | 面試的常客,經典的生產消費者模式

原文連結,求個關注

- END -

相關文章