go_channel

channel工作流程

发送者流程

1: 常规检查(发送一个已经关闭的chan会直接触发panic)
2: 查看接受则阻塞队列中是否有sudog(对应的一个goroutine,注意是dequeue操作),如果有则直接发送消息到阻塞的goroutine(gp.param = unsafe.Pointer(sg),直接进行指针赋值,具体见chan.go/send函数),并且唤醒接受goroutine,goto 6
3: 如果当前的阻塞队列没有满(sudog数量),则直接将发送的数据写入到发送的队列上(追加写),goto 6
4: 当前的的阻塞队列满了(缓冲的数量在此处对于等待队列中goroutine的数量),生成一个sudog,将当前的goroutine加入的发送者队列中,阻塞当前goroutine,直接有消费者消费完消息
5: 阻塞结束,释放当前的sudog
6: over

接受者流程

1: 常规检查(接收一个已经关闭的chan会直接return)
2: 查看接受则阻塞队列中是否有sudog(对应的一个goroutine),如果有则直接接受消息对于sudog的消息,goto 6
3: 检查缓冲队列中是否有数据,有就直接取 goto 6; 否则继续
4: (进入阻塞,条件是1:发送队列中没有sudog,2:缓冲区没有数据)将当前sudog加入接受等待队列中,并阻塞当前goroutine(调用goparkunlock)
5: 阻塞结束,释放当前的sudog
6: over

channel关闭流程

1: 常规检查
2: 遍历发送者/接受者队列中阻塞sudog,清空elem
3: 唤醒对于sudog对于的goroutine

channel数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//channel主结构,
/*
*发送者:未满写入,否则加入发送队列(阻塞)
*接受者:有数据就取,否则加入接受队列(阻塞)
*/
type hchan struct {
qcount uint // total data in the queue
dataqsiz uint // 缓冲总数量
buf unsafe.Pointer // 缓冲array指针
elemsize uint16
closed uint32
elemtype *_type // element type
sendx uint // send index
recvx uint // receive index
recvq waitq // 接收队列
sendq waitq // 发送队列
lock mutex
}
type waitq struct {
first *sudog
last *sudog
}

// sudog解释:
// 一个sudog对应的一个发送/接受消息的goroutine
type sudog struct {
g *g // 当前的goroutine
isSelect bool // 是否阻塞
next *sudog // 链表信息
prev *sudog // 链表信息
elem unsafe.Pointer // data element (may point to stack) // 元素指针
acquiretime int64
releasetime int64
ticket uint32
parent *sudog // semaRoot binary tree
waitlink *sudog // g.waiting list or semaRoot 阻塞信息
waittail *sudog // semaRoot
c *hchan // channel
}

ps:至于有缓冲和无缓冲(缓冲区0)的区别,直接套流程就知道区别了。

ps:源码位置go/src/runtime/chan.go