go通道基於go的併發排程實現,本身並不複雜,go併發排程請看我的這篇文章:go併發排程原理學習
1.channel資料結構
type hchan structtype 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) boolif 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 ...