Go channel 底層結(jié)構(gòu)及實(shí)現(xiàn)

1. 并發(fā)設(shè)計(jì)理念

Go 常被人提及的設(shè)計(jì)模式是:不要通過共享內(nèi)存的方式進(jìn)行通信,而應(yīng)該通過通信的方式共享內(nèi)存。

在很多語言中,多個(gè)線程傳遞數(shù)據(jù)的方式一般是共享內(nèi)存,為了解決線程競爭,需要限制同一時(shí)間能夠讀寫這些變量的線程數(shù)。但在go里提供了一種不同的并發(fā)模型——通信順序模型(communication sequential processes, CSP),goroutine 之間通過channel傳遞數(shù)據(jù)。

channel中的數(shù)據(jù)遵循先進(jìn)先出(FIFO)的設(shè)計(jì)。

2. 數(shù)據(jù)結(jié)構(gòu)

channel的操作封裝在runtime包下的chan.go文件,參見 /src/runtime/chan.go.

type hchan struct {

qcount? uint? ? ? ? ? // channel中環(huán)形隊(duì)列數(shù)據(jù)總數(shù),len()返回該值

dataqsiz uint? ? ? ? ? // 環(huán)形隊(duì)列的長度,make時(shí)指定,cap()返回該值

buf? ? ? unsafe.Pointer // 指向環(huán)形隊(duì)列的指針,緩存區(qū)基于環(huán)形隊(duì)列實(shí)現(xiàn)

elemsize uint16 // 元素的大小

closed? uint32 // channel關(guān)閉標(biāo)志

elemtype *_type // 元素類型

sendx? ? uint? // 向channel發(fā)送數(shù)據(jù)時(shí),寫入的位置索引

recvx? ? uint? // 從channel讀數(shù)據(jù)是,讀取的位置索引

recvq? ? waitq? // buf空時(shí),讀取的goroutine等待隊(duì)列

sendq? ? waitq? // buf滿時(shí),寫入的goroutine等待隊(duì)列

// 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 // 并發(fā)控制鎖,同一時(shí)刻,只允許一個(gè)

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

// 等待goroutine的雙向鏈表結(jié)構(gòu)

type waitq struct {

first *sudog

last? *sudog

}

1

2

3

4

5

3. 創(chuàng)建channel

func makechan(t *chantype, size int) *hchan {

var c *hchan

c = new(hchan)

c.buf = malloc(元素類型大小*size)

c.elemsize = 元素類型大小

c.elemtype = 元素類型

c.dataqsiz = size //channel 容量cap()

return c

}

1

2

3

4

5

6

7

8

9

10

3. 向channel 發(fā)送數(shù)據(jù)

發(fā)送數(shù)據(jù)調(diào)用runtime.chansend()方法

ch := make(chan int, 10)

ch <- 100

1

2

執(zhí)行流程:

1 如果等待接收隊(duì)列recvq不為空,說明緩沖區(qū)中沒有數(shù)據(jù)或者沒有緩沖區(qū),此時(shí)直接從recvq取出G,并把數(shù)據(jù)寫入,最后把該G喚醒,結(jié)束發(fā)送過程;

2 如果緩沖區(qū)中有空位置,將數(shù)據(jù)寫入緩沖區(qū)sendx位置,sendx++,qcount++,結(jié)束發(fā)送過程;

3 如果緩沖區(qū)中沒有空余位置,將待發(fā)送數(shù)據(jù)寫入G,將當(dāng)前G加入sendq,休眠,等待被讀goroutine喚醒;

4. 從channel接收數(shù)據(jù)

讀數(shù)據(jù)調(diào)用runtime.chanrecv()方法

ch := make(chan int, 10)

data := <-ch

1

2

執(zhí)行流程:

1 如果等待發(fā)送隊(duì)列sendq不為空,且沒有緩沖區(qū),直接從sendq中取出G,把數(shù)據(jù)直接,最后把G喚醒,結(jié)束讀取過程;

2 如果等待發(fā)送隊(duì)列sendq不為空,此時(shí)說明緩沖區(qū)已滿,從緩沖區(qū)中首部讀出數(shù)據(jù),把sendq出隊(duì)的G中數(shù)據(jù)寫入緩沖區(qū)尾部,把G喚醒,結(jié)束讀取過程;

3 如果緩沖區(qū)中有數(shù)據(jù),則從緩沖區(qū)取出數(shù)據(jù),結(jié)束讀取過程;

4 如何緩沖區(qū)為空,將當(dāng)前goroutine加入recvq,進(jìn)入休眠,等待被寫goroutine喚醒;

5. 關(guān)閉channel

1 首先校驗(yàn)chan是否已被初始化,然后加鎖之后再校驗(yàn)是否已被關(guān)閉過,如果校驗(yàn)都通過了,那么將closed字段設(shè)值為1;

2 遍歷所有的接收者和發(fā)送者,并將其goroutine 加入到glist中;

3 將所有g(shù)list中的goroutine加入調(diào)度隊(duì)列,等待被喚醒,這里需要注意的是發(fā)送者在被喚醒之后會(huì)panic;

————————————————

版權(quán)聲明:本文為CSDN博主「r_martian」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。

原文鏈接:https://blog.csdn.net/cjqh_hao/article/details/123948716

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

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

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