目錄?
channel背景
channel基本用法
channel應用場景
channel實現原理
channel數據結構
channel實現方式
channel注意事項
閑聊
歡迎加入我的公眾號【邁莫coding】 一起pk大廠
channel是Go的核心類型,是Go語言內置的類型,你無需引包,就能使用它。你可以把它看作一個管道,在Go語言中流傳著一句話,"執行業務處理的goroutine不要通過共享內存通信,要通過channel管道進行共享數據"。
channel和Go的另一種特性goroutine一起為并發編程提供了優雅的,便利的方案,來應對并發場景。
2channel基本用法channel的基本用法非常簡單,它提供了三種類型,分別為只能接收,只能發送,既能接收也能發送這三種類型。因此它的語法為:
chanstruct{} chan chan string // 既能接收也能發送
我們把既能發送也能接收的chan被稱為雙向chan,把只能接收或者只能發送的chan稱為單向chan。其中,"這個箭頭總是射向左邊的,元素類型總在最右邊。如果箭頭指向 chan,就表示可以往 chan 中塞數據;如果箭頭遠離 chan,就表示 chan 會往外吐數據。
通過make關鍵字,我們可以初始化一個chan,未初始化的chan的零值為nil。你可以設置他的容量,第二個參數為緩沖池的容量大小,也可以理解為即使chan未消費完,也可以存儲數據。
make(chan int, 8)
?
如果chan中還有數據,那么從這個chan中接收數據就不會阻塞,如果chan中數據未達到隊列容量,那么向該chan中存儲數據也不會阻塞,反之會阻塞。
還有一個知識點要記住:nil 是 chan 的零值,是一種特殊的 chan,對值是 nil 的 chan 的發送接收調用者總是會阻塞。
接下來,我們用代碼來學習一下chan的三種類型
只能接收數據的chan
代碼示例
package main import "fmt"// a 表示只能接收數據的chanfunc goChanA(a chan int) { b := fmt.Println("只能接收數據的channal[a]接收到的數據值為", b)}func main() { ch := make(chan int, 2) go goChanA(ch) // 往ch中寫入數據值 ch 2 time.Sleep(time.Second)}
結果
只能接收數據的channal[a]接收到的數據值為 2
?
只能發送數據的chan
代碼示例
package main import "fmtfunc main() { ch := make(chan ch }
往 chan 中發送一個數據使用“ch
這里的 ch 是 chan int 類型或者是 chan
3channel應用場景
數據交流:當作并發的 buffer 或者 queue,解決生產者 - 消費者問題。多個 goroutine 可以并發當作生產者(Producer)和消費者(Consumer)。
數據傳遞:一個goroutine將數據交給另一個goroutine,相當于把數據的擁有權托付出去。
信號通知:一個goroutine可以將信號(closing,closed,data ready等)傳遞給另一個或者另一組goroutine。
任務編排:可以讓一組goroutine按照一定的順序并發或者串行的執行,這就是編排功能。
鎖機制:利用channel實現互斥機制。
channel數據結構
channel一個類型管道,通過它可以在goroutine之間發送消息和接收消息。它是golang在語言層面提供的goroutine間的通信方式。
眾所周知,Go依賴于稱為CSP(Communicating Sequential Processes)的并發模型,通過 Channel實現這種同步模式。?
channel結構體
//path:src/runtime/chan.gotype hchan struct { qcount uint // 當前隊列列中剩余元素個數 dataqsiz uint // 環形隊列長度,即可以存放的元素個數 buf unsafe.Pointer // 環形隊列列指針 elemsize uint16 // 每個元素的?? closed uint32 // 標識關閉狀態 elemtype *_type // 元素類型 sendx uint // 隊列下標,指示元素寫?入時存放到隊列列中的位置 x recvx uint // 隊列下標,指示元素從隊列列的該位置讀出 recvq waitq // 等待讀消息的goroutine隊列 sendq waitq // 等待寫消息的goroutine隊列 lock mutex // 互斥鎖,chan不允許并發讀寫}
從數據結構可以看出channel由隊列、類型信息、goroutine等待隊列組成。? ? ? ?
channel實現方式
chan內部實現了一個緩沖隊列作為緩沖區,隊列的長度是創建chan時指定的。
下圖展示了可緩存6個元素的channel示意圖:
dataqsiz:指向隊列的長度為6,即可緩存6個元素
buf:指向隊列的內存,隊列中還剩余兩個元素
qcount:當前隊列中剩余的元素個數
sendx:指后續寫入元素的位置
recvx:指從該位置讀取數據
等待隊列
從channel中讀數據,如果channel緩沖區為空或者沒有緩沖區,當前goroutine會被阻塞;向channel中寫數據,如果channel緩沖區已滿或者沒有緩沖區,當前goroutine會被阻塞。
被阻塞的goroutine將會被掛在channel的等待隊列中:
因讀阻塞的goroutine會被向channel寫入數據的goroutine喚醒
因寫阻塞的goroutine會被從channel讀數據的goroutine喚醒
下面展示了一個沒有緩沖區的channel,有幾個goroutine阻塞等待數據:
注意,一般情況下recvq和sendq至少有一個為空。只有一個例外,那就是同一個goroutine使用select語句向channel一邊寫數據一邊讀數據。
向channel寫數據
流程圖:
詳細過程
如果recvq隊列不為空,說明緩沖區沒有數據或者沒有緩沖區,此時直接從recvq等待隊列中取出一個G,并把數據寫入,最后把該G喚醒,結束發送過程;
如果緩沖區有空余位置,則把數據寫入緩沖區中,結束發送過程;
如果緩沖區沒有空余位置,則把數據寫入G,將當前G寫入sendq隊列,進入休眠,等待被讀goroutine喚醒;
從channel讀數據
流程圖
詳細過程
如果等待發送隊列sendq不為空,且沒有緩沖區,直接從sendq隊列中取出G,把G中數據讀出,最后把G喚醒,結束讀取過程;
如果等待發送隊列sendq不為空,說明緩沖區已滿,從緩沖隊列中首部讀取數據,從sendq等待發送隊列中取出G,把G中的數據寫入緩沖區尾部,結束讀取過程;
如果緩沖區中有數據,則從緩沖區取出數據,結束讀取過程;
向已經關閉的channel中寫入數據會發生Panic
關閉已經關閉的channel會發生Panic
關閉值為nil的channel會發生Panic
6閑聊
讀完文章,自己是不是和channel管道的cp率又提高了
我是邁莫,歡迎大家和我交流
原創不易,覺得文章寫得不錯的小伙伴,點個贊? 鼓勵一下吧~
7歡迎加入我的公眾號【邁莫coding】 一起pk大廠
邁莫coding歡迎客官的到來