今天是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關閉的時間了。
今天的文章到這里就結束了,如果喜歡本文的話,請來一波素質三連,給我一點支持吧(關注、轉發、點贊)。