Go channel實現原始碼分析

2022-02-27 01:44:09 字數 3772 閱讀 1018

go通道基於go的併發排程實現,本身並不複雜,go併發排程請看我的這篇文章:go併發排程原理學習

1.channel資料結構

type hchan struct

type waitq

struct

2.建立channel實現

建立channel例項:

ch := make(chan int, 4)

實現函式:

func makechan(t *chantype, size int64) *hchan

大致實現:

執行上面這行**會new乙個hchan結構,同時建立乙個dataqsiz=4的int型別的迴圈佇列,其實就是乙個容納4個元素的陣列,就是按順序往裡面寫資料,寫滿之後又從0開始寫,這個順序索引就是hchan.sendx

3.傳送資料

傳送資料例項:

ch <- 100

傳送資料實現函式:

func chansend(c *hchan, ep unsafe.pointer, block bool, callerpc uintptr) bool

ep指向要傳送資料的首位址

func chansend(c *hchan, ep unsafe.pointer, block bool, callerpc uintptr) bool

if sg := c.recvq.dequeue(); sg !=nil , 3

)

return

true}

if c.qcount c.qcount++unlock(&c.lock

)

return

true}

if !block

//以上都是同步非阻塞的,ch <- 100直接返回

//以下是同步阻塞

//緩衝區滿了,也沒有接收者,通道將被阻塞,其實就是不執行當前g了,將狀態改成等待狀態

gp :=getg()

mysg :=acquiresudog()

c.sendq.enqueue(mysg)

goparkunlock(&c.lock, "

chan send

", traceevgoblocksend, 3)

//當g被喚醒,狀態改成可執行狀態,從這裡開始繼續執行

releasesudog(mysg)

return

true

}

大致實現:

1:接收佇列不為空,從接收佇列中取出第乙個接收者*sudog,將資料複製到sudog.elem,複製函式為memmove用彙編實現,通知接收方資料給你了,將接收方協程由等待狀態改成可執行狀態,將當前協程加入協程佇列,等待被排程。

2:沒有接收者,有緩衝區且沒有滿,直接將資料複製到緩衝中,寫入緩衝區的位置為hchan.buf[sendx++],如果緩衝區已滿sendx=0,就是迴圈佇列的實現,往sendx指定的位置寫資料,hchan.qcount++

3:沒有接收者,沒有緩衝區或是滿了,則從當前協程對應的p的sudog佇列中取乙個struct sudog,將資料複製到sudog.elem,將sudog加入sendq佇列中,通知接收方,當前流程阻塞,等待被喚醒,接收方收到通知後(被喚醒),繼續往下執行,接收資料完成後會通知傳送方,即將傳送方協程狀態由等待狀態改成可執行狀態,加入協程可執行佇列,等著被執行不會阻塞的情況:

1:通道緩衝區沒有滿之前,因為只是將要傳送的資料複製到緩衝區就返回了

2:有接收者的情況,有資料複製到接收方的資料結構中(不是最終接收資料的變數,在執行接收函式的時候會拷貝到最終接收資料的變數),喚醒接收協程會阻塞的情況:自然就是緩衝區滿了,也沒有接收方,這個時候會將資料打包放到傳送佇列,當前協程被設定成等待狀態,這個狀態不會被排程,當有接收方收到資料後,才會被喚醒

4.接收資料

接收資料例項:

val := <- ch

接收資料實現函式:

func chanrecv(c *hchan, ep unsafe.pointer, block bool) (selected, received bool

)func chanrecv(c *hchan, ep unsafe.pointer, block bool) (selected, received bool

) , 3

)

return

true, true}

if c.qcount > 0

typedmemclr(c.elemtype, qp)

c.recvx++

if c.recvx ==c.dataqsiz

c.qcount--unlock(&c.lock

)

return

true, true}

if !block

//以上同步非阻塞

//以下同步阻塞

gp :=getg()

mysg :=acquiresudog()

c.recvq.enqueue(mysg)

//將當前g狀態改成等待狀態,停止排程

goparkunlock(&c.lock, "

chan receive

", traceevgoblockrecv, 3)

//當前g被喚醒從這裡繼續執行

mysg.c =nil

releasesudog(mysg)

return

true, !closed

}

大致實現:

1.傳送佇列不為空(說明緩衝區已滿),從傳送佇列中取出第乙個傳送者*sudog

1.1.沒有緩衝區,直接將傳送佇列中的資料sudog.elem複製出來,存到接收資料的變數val中,通知傳送方我處理完了,你可以繼續執行

1.2.有緩衝區,複製出緩衝區hchan.buf[recvx]對應的元素到val,在將傳送方sudog.elem複製到hchan.buf[recvx],傳送方按順序寫,接收方按順序讀,典型的fifo,為了保證是先進先出,所以先複製出,再將佇列首元素複製到對應的緩衝區中,其實就是傳送佇列連線在緩衝區後面,緩衝區滿了,就寫佇列,接收的時候先從緩衝區中拿資料,拿掉之後空出來的位置從傳送佇列中取第乙個填滿,並喚醒對應的g,只要傳送佇列不為空,緩衝區肯定會被填滿

2.傳送隊列為空,緩衝區不為空,複製出緩衝區hchan.buf[recvx]對應的元素到val,hchan.qcount--

3.傳送隊列為空,緩衝區也為空,那就是沒有任何待接收的資料,接收流程就只能等了,將接收資訊打包成sudog,加入接收佇列recvq,當前執行流程阻塞,等有傳送資料後會被喚醒繼續

5.channel fifo在解釋一次

5.1:緩衝區沒滿,傳送資料就是進緩衝佇列,接收資料就是出緩衝佇列,比較好理解

5.2:緩衝區已滿,傳送資料就是進等待佇列,接收資料先出緩衝佇列,即為要接收的資料,等待佇列出列,將資料存在緩衝佇列剛出列的位置,剛出列的位置相當於緩衝佇列的末尾,也就是說等待佇列的列頭連在緩衝佇列的末尾,將等待佇列的列頭加入快取佇列的列尾,保證了緩衝佇列是滿的,減少的是緩衝佇列中的資料,保證先進先出

5.3:接收資料,緩衝佇列或等待佇列有資料,拿走第乙個,保證等待佇列是接在緩衝區末尾,即緩衝區末尾有空缺,就讓等待佇列出列,並填充至緩衝區末尾,否則將自己打包加入接收佇列,當前g進入等待狀態,有資料傳送自然會通知你

總結:go channel基於go的併發排程實現阻塞和非阻塞兩種通訊方式

GO channel實現同步

goroutine執行在相同的位址空間,因此訪問共享記憶體必須 做好同步。goroutine奉行通過通訊來共享記憶體,而不是共享記憶體通訊 它跟map一樣,使用make來建立,它是乙個引用 而不是值傳遞 make chan type,capacity channel value 傳送value到ch...

Cartographer原始碼篇 原始碼分析 1

在安裝編譯cartographer 1.0.0的時候,我們可以看到 主要包括cartorgarpher ros cartographer ceres sover三個部分。其中,ceres solver用於非線性優化,求解最小二乘問題 cartographer ros為ros平台的封裝,獲取感測器資料...

ThreadLocal實現原理與原始碼分析

threadlocal底層實現內部類 threadlocalmap 一 threadlocal的set方法原始碼分析 1 public void set t value thread t thread.currentthread threadlocalmap map getmap t if map ...