(六)golang channel 源碼分析

基礎(chǔ)用法

channel

ch := make(chan int)
senderOnly := make(chan<- int)   // 只能用來發(fā)送(管道的入口,只進(jìn)不出)
    receiverOnly := make(<-chan int) // 只能用來接收(管道的出口,只出不進(jìn))
    unbuffer := make(chan int)       // 無緩沖可收發(fā)
    buffer := make(chan int, 2)      // 有緩沖可收發(fā)

chan T是雙向channel類型,編譯器允許對雙向channel同時(shí)進(jìn)行發(fā)送和接收。
chan<- T是只寫channel類型,編譯器只允許往channel里面發(fā)送數(shù)據(jù)。
<-chan T是只讀channel類型,編輯器只允許從channel里面接收數(shù)據(jù)。
所謂單向channel只是對channel的一種使用限制,實(shí)際定義單向通道沒有意義,一般用在限制函數(shù)返回值或者函數(shù)參數(shù)。
func readChan(chanName <-chan int): 通過形參限定函數(shù)內(nèi)部只能從channel中讀取數(shù)據(jù)
func writeChan(chanName chan<- int): 通過形參限定函數(shù)內(nèi)部只能向channel中寫入數(shù)據(jù)
源碼位置:https://github.com/golang/go/blob/master/src/runtime/chan.go

源碼

type hchan struct {
    qcount   uint           // 當(dāng)前隊(duì)列中剩余元素
    dataqsiz uint           // 環(huán)形隊(duì)列長度
    buf      unsafe.Pointer // 環(huán)形隊(duì)列指針
    elemsize uint16
    closed   uint32
    elemtype *_type // element type
    sendx    uint   // 寫入時(shí)存放到隊(duì)列中的位置
    recvx    uint   // 隊(duì)列的該位置讀出
    recvq    waitq  // 等待讀消息的goroutine隊(duì)列
    sendq    waitq  // 等待寫消息的goroutine隊(duì)列
    lock mutex // 鎖保護(hù)
}

type waitq struct {
    first *sudog
    last  *sudog
}

環(huán)形隊(duì)列(緩沖通道)

chan內(nèi)部實(shí)現(xiàn)了一個(gè)環(huán)形隊(duì)列作為其緩沖區(qū),隊(duì)列的長度是創(chuàng)建chan時(shí)指定的。下圖展示了一個(gè)可緩存6個(gè)元素的channel示意圖:


656024E4-9DEA-4BB6-A424-CCC4B8DB7CD6.png

dataqsiz指示了隊(duì)列長度為6,即可緩存6個(gè)元素;
buf指向隊(duì)列的內(nèi)存,隊(duì)列中還剩余兩個(gè)元素;
qcount表示隊(duì)列中還有兩個(gè)元素;
sendx指示后續(xù)寫入的數(shù)據(jù)存儲的位置,取值[0, 6);
recvx指示從該位置讀取數(shù)據(jù), 取值[0, 6);

等待隊(duì)列(無緩沖通道)

從channel讀數(shù)據(jù),如果channel緩沖區(qū)為空或者沒有緩沖區(qū),當(dāng)前goroutine會(huì)被阻塞。 向channel寫數(shù)據(jù),如果channel緩沖區(qū)已滿或者沒有緩沖區(qū),當(dāng)前goroutine會(huì)被阻塞。
下圖展示了一個(gè)沒有緩沖區(qū)的channel,有幾個(gè)goroutine阻塞等待讀數(shù)據(jù):


59C514B7-BF69-40AB-BCD5-2FF0440072E8.png

一般情況下recvq和sendq至少有一個(gè)為空。只有一個(gè)例外,那就是同一個(gè)goroutine使用select語句向channel一邊寫數(shù)據(jù),一邊讀數(shù)據(jù)。

關(guān)閉channel

關(guān)閉channel時(shí)會(huì)把recvq中的G全部喚醒,會(huì)讀取完已經(jīng)放入的數(shù)據(jù)后關(guān)閉。把sendq中的G全部喚醒,但這些G會(huì)panic。
除此之外,panic出現(xiàn)的常見場景還有: 1. 關(guān)閉值為nil的channel 2. 關(guān)閉已經(jīng)被關(guān)閉的channel 3. 向已經(jīng)關(guān)閉的channel寫數(shù)據(jù)

package main

import (
    "fmt"
    "time"
)

func main() {
    chan1 := make(chan int, 9)

    go func() {
        chan1 <- 1
        chan1 <- 2
        chan1 <- 3
        chan1 <- 4
        close(chan1)
        fmt.Println("已經(jīng)關(guān)閉")
        time.Sleep(5 * time.Second)
    }()
    for {
        x, ok := <-chan1
        if !ok {
            fmt.Println("關(guān)閉")
        }
        fmt.Println(x)
        time.Sleep(2 * time.Second)
    }
}

已經(jīng)關(guān)閉
1
2
3
4
關(guān)閉
0
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容