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