GO的內置數據結構-channel

1、channel

channel分爲有buffer的和沒有buffer的。
沒有buffer的能夠當成有buffer可是buffersize爲0的狀況。
buffer數據結構:數組

type hchan struct {
    qcount   uint           // 當前chan中有多少數據
    dataqsiz uint           // 環形數組隊列的大小,也就是咱們定義的緩衝區大小
    buf      unsafe.Pointer // 指向環形數組隊列的指針
    elemsize uint16
    closed   uint32
    elemtype *_type // element type
    sendx    uint   // 發送時插入的位置(環形數組的下標)
    recvx    uint   // 接收時取數據的位置(環形數組的下標)
    recvq    waitq  // 接收鏈表,當buf爲空的時候,打包goroutine現場後放在這裏
    sendq    waitq  // 發送鏈表,當buf滿的時候,打包goroutine現場後放在這裏
    // 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
}

發送流程


像圖中發送數據到channel中,每次qcount和sendx會隨之變化,sendx會在插入前標誌當前的插入位置變到插入後標誌下一個數據插入位置(因爲是環形數組,因此若是在最後位置插入後索引歸0
當buf裏面的數據滿的時候,再往裏面發送數據,此時qcount==dataqsize表示滿,此時咱們會將當前G的現場與channel打包成一個sudog的結構,鏈在sendq上。數據結構

上圖爲正常的發送流程,用以演示各個字段在流程中的變化。事實上發送時還須要判斷recvq鏈表是否有sudog:

咱們知道,sendq中存放的是等待發送的sudog,那麼一樣的recvq存放的就是等待接收的sudog。能夠想象到,當recvq中有sudog節點的時候就說明咱們的緩衝區已經沒有數據能夠取了,纔會將接收的g放到recvq中。此時,咱們須要發送的內容應該當即被拿走,不應再放到buf或sendq中。
完整的發送流程以下:
ui

接收流程

一樣的,當作從channel中接收數據的動做時,會先判斷buf是否爲空,爲空的話進行現場打包成sudog鏈在recvq的鏈表上。
完整的接收流程以下:

與發送流程有所不一樣的是,當buf數據滿而且sendq中有sudog的時候,咱們還須要判斷是否有緩衝區。this

  1. 若是有緩衝區的話咱們須要維護buf:spa

    1. 先將當前的recvx索引的數據取出
    2. 而後將sudog中的elem數據取出
    3. 再將sudog取出的數據copy到buf空出來的位置。(sendq和recvq是鏈表結構可是也符合先進先出,在waiq結構中會保存first sudog和last sudog的指針位置,方便進行鏈表的入隊與出隊操做)
  2. 若是沒有緩衝區,那咱們直接就能夠將sudog的數據取出接收。
爲何發送的時候不須要判斷是否有緩衝區而接收的時候須要判斷呢?

咱們能夠從接收流程中發現,咱們會在buf爲空的時候纔會往recvq追加sudog,那麼也就是說在接收流程中,recvq只要不爲空那就說明buf是空的,那麼沒有緩衝區和有緩衝區也都是等價於空的buf,因此無需判斷。
可是在接收流程中,若是sendq不爲空的話。指針

  • 若是有緩衝區,說明buf必定是滿的,由於須要維護好前後順序,因此咱們要維護buf和sendq鏈表。
  • 沒有緩衝區,無需維護buf,因此直接從sendq中找數據內容。
ps:
  • gopark()是掛起的意思,會對應一個goready()喚醒。
  • 掛起與喚醒:code

    • ①sender掛起的必定是由receiver或close喚醒
    • ②receiver掛起的必定是由sender或close喚醒。
相關文章
相關標籤/搜索