今天是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{7, 2, 8, -9, 4, 0}
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 int, 100)
比如這樣,我們就建立了一個緩衝區為100的通道。
但多說一句,其實這種情況不太常用,原因也很簡單。因為上下游的消費情況是統一的,如果生產者生產的速度過快,而消費端跟不上的話,即使把它先暫存在緩衝區當中也沒什麼用,早晚還是會要阻塞的。
close
當我們對通道使用結束之後,可以通過close語句將它關閉。
Close這個操作只能在生產端進行,消費端如果close通道會引發一個panic。我們在從chan接收資料的時候,可以加上一個引數判斷通道是否關閉。
v, ok := <- ch
if !ok {
return
}
這樣我們就可以判斷chan關閉的時間了。
今天的文章到這裡就結束了,如果喜歡本文的話,請來一波素質三連,給我一點支援吧(關注、轉發、點贊)。