Golang channel 的實現(xiàn)原理

Golang channel 的實現(xiàn)原理


Channel 是golang語言自身提供的一種非常重要的語言特性罪郊, 它是實現(xiàn)任務(wù)執(zhí)行隊列、 協(xié)程間消息傳遞尚洽、高并發(fā)框架的基礎(chǔ)悔橄。關(guān)于channel的用法的文章已經(jīng)很多, 本文從channel源碼的實現(xiàn)的角度腺毫, 討論一下其實現(xiàn)原理癣疟。

關(guān)于channel放在: src/runtime/chan.go
channel的關(guān)鍵的結(jié)構(gòu)體放在hchan里面, 它記錄了channel實現(xiàn)的關(guān)鍵信息拴曲。

type hchan struct { 
qcount uint // total data in the queue 
dataqsiz uint // size of the circular queue 
buf unsafe.Pointer // points to an array of dataqsiz elements 
elemsize uint16 
closed uint32 
elemtype *_type // element type 
sendx uint // send index 
recvx uint // receive index 
recvq waitq // list of recv waiters 
sendq waitq // list of send waiters

// lock protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex
}

創(chuàng)建channel
用法: ch := make(chan TYPE争舞, size int);
這里, type是channel里面?zhèn)鬟f的elem的類型;
size是channel緩存的大械种:
如果為1矩桂, 代表非緩沖的channel, 表明channel里面最多有一個elem床牧, 剩余的只能在channel外排隊等待荣回;
如果為0,
其實現(xiàn)代碼如下:

func makechan(t *chantype, size int) *hchan { 
// Hchan does not contain pointers interesting for GC when elements stored in buf do not contain pointers. 
// buf points into the same allocation, elemtype is persistent. 
// SudoG’s are referenced from their owning thread so they can’t be collected. 
// TODO(dvyukov,rlh): Rethink when collector can move allocated objects. 
var c *hchan 
switch { 
case size == 0 || elem.size == 0: 
// Queue or element size is zero. 
c = (*hchan)(mallocgc(hchanSize, nil, true)) 
// Race detector uses this location for synchronization. 
c.buf = unsafe.Pointer(c) 
case elem.kind&kindNoPointers != 0: 
// Elements do not contain pointers. 
// Allocate hchan and buf in one call. 
c = (*hchan)(mallocgc(hchanSize+uintptr(size)*elem.size, nil, true)) 
c.buf = add(unsafe.Pointer(c), hchanSize) 
default: 
// Elements contain pointers. 
c = new(hchan) 
c.buf = mallocgc(uintptr(size)*elem.size, elem, true) 
}

c.elemsize = uint16(elem.size)
c.elemtype = elem
c.dataqsiz = uint(size)

if debugChan {
    print("makechan: chan=", c, "; elemsize=", elem.size, "; elemalg=", elem.alg, "; dataqsiz=", size, "\n")
}
return c

}

寫入channel elem
用法: ch <- elem
channel是阻塞時的管道戈咳, 從channel讀取的時候心软,可能發(fā)生如下3種情況:
channel 已經(jīng)關(guān)閉壕吹, 發(fā)生panic;
從已經(jīng)收到的隊列內(nèi)讀取一個elem删铃;
從緩存隊列內(nèi)讀取elem耳贬;
lock(&c.lock)

    if c.closed != 0 {
        unlock(&c.lock)
        panic(plainError("send on closed channel"))
    }

    if sg := c.recvq.dequeue(); sg != nil {
        // Found a waiting receiver. We pass the value we want to send
        // directly to the receiver, bypassing the channel buffer (if any).
        send(c, sg, ep, func() { unlock(&c.lock) }, 3)
        return true
    }

    if c.qcount < c.dataqsiz {
        // Space is available in the channel buffer. Enqueue the element to send.
        qp := chanbuf(c, c.sendx)
        if raceenabled {
            raceacquire(qp)
            racerelease(qp)
        }
        typedmemmove(c.elemtype, qp, ep)
        c.sendx++
        if c.sendx == c.dataqsiz {
            c.sendx = 0
        }
        c.qcount++
        unlock(&c.lock)
        return true
    }

    if !block {
        unlock(&c.lock)
        return false
    }

從channel elem讀取elem
用法: elem, ok := <- ch
if !ok {
fmt.Println(“ch has been closed”)
}
3種可能返回值:

chan已經(jīng)被關(guān)閉, OK為false猎唁, elem為該類型的空值咒劲;
如果chan此時沒有值存在, 該讀取語句會一直等待直到有值诫隅;
如果chan此時有值腐魂, 讀取正確的值, ok為true逐纬;
代碼實現(xiàn):

// chanrecv receives on channel c and writes the received data to ep.
// ep may be nil, in which case received data is ignored.
// If block == false and no elements are available, returns (false, false).
// Otherwise, if c is closed, zeros *ep and returns (true, false).
// Otherwise, fills in *ep with an element and returns (true, true).
// A non-nil ep must point to the heap or the caller's stack.
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
          // 正常的channel讀取流程蛔屹, 直接讀取
     if sg := c.sendq.dequeue(); sg != nil {
        // Found a waiting sender. If buffer is size 0, receive value
        // directly from sender. Otherwise, receive from head of queue
        // and add sender's value to the tail of the queue (both map to
        // the same buffer slot because the queue is full).
        recv(c, sg, ep, func() { unlock(&c.lock) }, 3)
        return true, true
    }

 // 從緩存的elem隊列讀取一個elem  
  if c.qcount > 0 {
        // Receive directly from queue
        qp := chanbuf(c, c.recvx)
        if raceenabled {
            raceacquire(qp)
            racerelease(qp)
        }
        if ep != nil {
            typedmemmove(c.elemtype, ep, qp)
        }
        typedmemclr(c.elemtype, qp)
        c.recvx++
        if c.recvx == c.dataqsiz {
            c.recvx = 0
        }
        c.qcount--
        unlock(&c.lock)
        return true, true
    }   

  // channel沒有可以讀取的elem, 更新channel內(nèi)部狀態(tài)豁生, 等待新的elem兔毒;   
}

關(guān)閉channel
用法: close(ch)
作用: 關(guān)閉channel, 并將channel內(nèi)緩存的elem清除沛硅;

代碼實現(xiàn):

func closechan(c *hchan) {
    var glist *g

    // release all readers
    for {
        sg := c.recvq.dequeue()
        gp := sg.g
        gp.param = nil
        gp.schedlink.set(glist)
        glist = gp
    }

    // release all writers (they will panic)
    for {
        sg := c.sendq.dequeue()
        sg.elem = nil
        gp := sg.g
        gp.param = nil
        gp.schedlink.set(glist)
        glist = gp
    }
    unlock(&c.lock)

    // Ready all Gs now that we've dropped the channel lock.
    for glist != nil {
        gp := glist
        glist = glist.schedlink.ptr()
        gp.schedlink = 0
        goready(gp, 3)
    }
}

??????每天堅持學習1小時Go語言眼刃,大家加油,我是彬哥摇肌,下期見擂红!如果文章中不同觀點、意見請文章下留言或者關(guān)注下方訂閱號反饋围小!


社區(qū)交流群:221273219
Golang語言社區(qū)論壇 :
www.Golang.Ltd
LollipopGo游戲服務(wù)器地址:
https://github.com/Golangltd/LollipopGo
社區(qū)視頻課程課件GIT地址:
https://github.com/Golangltd/codeclass


Golang語言社區(qū)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末昵骤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子肯适,更是在濱河造成了極大的恐慌变秦,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件框舔,死亡現(xiàn)場離奇詭異蹦玫,居然都是意外死亡,警方通過查閱死者的電腦和手機刘绣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門樱溉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人纬凤,你說我怎么就攤上這事福贞。” “怎么了停士?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵挖帘,是天一觀的道長完丽。 經(jīng)常有香客問我,道長拇舀,這世上最難降的妖魔是什么逻族? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮你稚,結(jié)果婚禮上瓷耙,老公的妹妹穿的比我還像新娘。我一直安慰自己刁赖,他們只是感情好搁痛,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著宇弛,像睡著了一般鸡典。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上枪芒,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天彻况,我揣著相機與錄音,去河邊找鬼舅踪。 笑死纽甘,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的抽碌。 我是一名探鬼主播悍赢,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼货徙!你這毒婦竟也來了左权?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤痴颊,失蹤者是張志新(化名)和其女友劉穎赏迟,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蠢棱,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡锌杀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了泻仙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抛丽。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖饰豺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情允蜈,我是刑警寧澤冤吨,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布蒿柳,位于F島的核電站,受9級特大地震影響漩蟆,放射性物質(zhì)發(fā)生泄漏垒探。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一怠李、第九天 我趴在偏房一處隱蔽的房頂上張望圾叼。 院中可真熱鬧,春花似錦捺癞、人聲如沸夷蚊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽惕鼓。三九已至,卻和暖如春唐础,著一層夾襖步出監(jiān)牢的瞬間箱歧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工一膨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留呀邢,地道東北人。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓豹绪,卻偏偏與公主長得像价淌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子森篷,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350

推薦閱讀更多精彩內(nèi)容