底层数据结构
type hchan struct {
qcount uint // channel以有数据大小
dataqsiz uint // channel大小
buf unsafe.Pointer // 有缓存的channel,其buf地址
elemsize uint16
closed uint32
elemtype *_type // channel中元素类型
sendx uint // 发送队列索引
recvx uint // 接收队列索引
recvq waitq // 接收队列;读协程等待列表
sendq waitq // 发送队列;写协程等待列表
lock mutex // channel全局操作时需要有锁住
}
type waitq struct {
firsxt *sudog
last *sudog
}
channel
底层buf
缓存通道数据recvq/sendq
存放阻塞的接收协程/发送协程
sudog
表示一个在等待队列中的g对象,是一个双向链表,主要包含协程本身,和前后指针g *g
next *sudog
prev *sudog
waitq
是一个包含sudog
的双向链表,在sudog
的基础上,增加首位指针
channel等待队列读写
func (q *waitq) enqueue(sgp *sudog) {
sgp.next = nil
x := q.last
if x == nil {
sgp.prev = nil
q.first = sgp
q.last = sgp
return
}
sgp.prev = x
x.next = sgp
q.last = sgp
}
func (q *waitq) dequeue() *sudog {
for {
sgp := q.first
if sgp == nil {
return nil
}
y := sgp.next
if y == nil {
q.first = nil
q.last = nil
} else {
y.prev = nil
q.first = y
sgp.next = nil // mark as removed (see dequeueSudog)
}
...
return sgp
}
}
- channel等待队列
sendq, recvq
均为双向链表 - 队列插入
enqueue
: 双向链表插入操作,看起来没有上限 - 队列取出
dequeue
: 双向链表取出操作, 队列为空是返回nil- for循环是处理在select语句中的阻塞操作
channel新建
func makechan(t *chantype, size int) *hchan {
// 计算和校验需要申请的buf内存大小
elem := t.elem
mem, overflow := math.MulUintptr(elem.size, uintptr(size))
if overflow || mem > maxAlloc-hchanSize || size < 0 {
panic(plainError("makechan: size out of range"))
}
// 这一段其实不是很懂,但整体上是内存申请
var c *hchan
switch {
// 零缓存通道,无需buffer,只用申请hchan本身大小即可
case mem == 0:
c = (*hchan)(mallocgc(hchanSize, nil, true))
c.buf = c.raceaddr()
// elem不包含指针???不懂
case elem.ptrdata == 0:
c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
c.buf = add(unsafe.Pointer(c), hchanSize)
// 默认情况下,创建一个hchan结构体,然后申请buf内存
default:
c = new(hchan)
c.buf = mallocgc(mem, elem, true)
}
c.elemsize = uint16(elem.size)
c.elemtype = elem
c.dataqsiz = uint(size)
return c
}
- channel新建,整体上即初始化hchan结构体和buff需要的内存
buff
区没有结构,只是一段通用的内存,然后通过指针进行寻址
channel发送消息
// channel单词发送逻辑(这里只是尝试一次消息发送)
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
// 如果不阻塞、chan未关闭、buff已满,直接返回false(发送失败)
if !block && c.closed == 0 && full(c) {
return false
}
// 全局加锁
lock(&c.lock)
// 如果此时chan是关闭状态(其他协程关闭),panic
if c.closed != 0 {
unlock(&c.lock)
panic(plainError("send on closed channel"))
}
// 从chan接收队列中取出一个协程(该协程在接收chan消息时被放进recvq)
// 如果接收队列中存在有效协程,则直接将消息内容复制到取出协程的栈区(sg.elem),并返回发送成功
if sg := c.recvq.dequeue(); sg != nil {
send(c, sg, ep, func() { unlock(&c.lock) }, 3)
return true
}
// 走到这里,说明接收队列为空了
// 如果当前消息数小于chan缓存大小,将数据复制到chan缓冲区,同时注意增加sendx,返回发送成功
if c.qcount < c.dataqsiz {
qp := chanbuf(c, c.sendx)
typedmemmove(c.elemtype, qp, ep)
c.sendx++
if c.sendx == c.dataqsiz {
c.sendx = 0
}
c.qcount++
unlock(&c.lock)
return true
}
// 这里,说明不仅接收队列空了,chan缓存也满了
// 如果不阻塞,直接返回发送失败
if !block {
unlock(&c.lock)
return false
}
// 如果需要阻塞,则新组装sudog对象,并将sudog写入chan.sendq (此时发送协程处于阻塞态)
gp := getg()
mysg := acquireSudog()
mysg.releasetime = 0
if t0 != 0 {
mysg.releasetime = -1
}
mysg.elem = ep
mysg.waitlink = nil
mysg.g = gp
mysg.isSelect = false
mysg.c = c
gp.waiting = mysg
gp.param = nil
c.sendq.enqueue(mysg)
...
return true
}
func send(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
if sg.elem != nil {
sendDirect(c.elemtype, sg, ep)
sg.elem = nil
}
...
}
// 从src地址中,赋值t大小数据到sg.elem - 协程栈数据区
func sendDirect(t *_type, sg *sudog, src unsafe.Pointer) {
dst := sg.elem
...
memmove(dst, src, t.size)
}
// memmove copies n bytes from "from" to "to".
func memmove(to, from unsafe.Pointer, n uintptr)
总结:
- channel发送消息优先级:
- 接收队列中取
waiting goroutine
,存在则直接将数据复制到接收协程的数据区(elem) - 无
waiting gorouting
,则尝试将数据写入channel缓冲区暂存 - 如果channel 缓冲区也满了,则看调用方是否需要阻塞(调用方是谁???)
- 不阻塞,直接返回数据写入失败
- 阻塞,则新建sudog,并同步数据指针,然后存入
channel sendq
(从enqueue的代码看,sendq/waitq大小无上限限制)
- 接收队列中取
- 消息发送成功的底层逻辑:
- 不管是直接发送给接收协程,还是写入channel缓冲,消息传递的方式都是数据的复制
channel接收消息
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
// 如果sendq中有等待发送数据的协程,则执行recv,会根据channel是否有buffer做不同处理
// 无buffer,则直接将等待队列中goroutine数据复制给接收者
// 有buffer,则从buffer中获取数据给接受者,再把待发送goroutine的数据写到buffer里
if sg := c.sendq.dequeue(); sg != nil {
recv(c, sg, ep, func() { unlock(&c.lock) }, 3)
return true, true
}
// 没有等待的发送协程
// 如果此时channel buffer中有数据,从buffer中取出数据并赋值给ep
if c.qcount > 0 {
qp := chanbuf(c, c.recvx)
if ep != nil {
typedmemmove(c.elemtype, ep, qp)
}
typedmemclr(c.elemtype, qp)
c.recvx++
if c.recvx == c.dataqsiz {
c.recvx = 0
}
c.qcount--
unlock(&c.lock)
return true, true
}
// 如果不阻塞,直接返回
if !block {
unlock(&c.lock)
return false, false
}
// 否则新组装sudog,并将其enqueue到recvq中
// 接收协程也出于阻塞状态
gp := getg()
mysg := acquireSudog()
mysg.releasetime = 0
if t0 != 0 {
mysg.releasetime = -1
}
mysg.elem = ep
mysg.waitlink = nil
gp.waiting = mysg
mysg.g = gp
mysg.isSelect = false
mysg.c = c
gp.param = nil
c.recvq.enqueue(mysg)
...
return true, !closed
}
func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
// 如果是无缓存channel,直接把sudog的数据拷贝给ep地址
if c.dataqsiz == 0 {
if raceenabled {
racesync(c, sg)
}
if ep != nil {
recvDirect(c.elemtype, sg, ep)
}
} else {
// Queue is full. Take the item at the
// head of the queue. Make the sender enqueue
// its item at the tail of the queue. Since the
// queue is full, those are both the same slot.
// 从channel buffer中取出一条数据,并拷贝给目的地址ep
qp := chanbuf(c, c.recvx)
if ep != nil {
typedmemmove(c.elemtype, ep, qp)
}
typedmemmove(c.elemtype, qp, sg.elem)
c.recvx++
if c.recvx == c.dataqsiz {
c.recvx = 0
}
c.sendx = c.recvx // c.sendx = (c.sendx+1) % c.dataqsiz
}
...
}
// 从sudog的数据区中,拷贝一个对象到dst。这一个对象其实就是该类型的字节数大小
func recvDirect(t *_type, sg *sudog, dst unsafe.Pointer) {
src := sg.elem
...
memmove(dst, src, t.size)
}
总结:
- channel接收消息优先级:
- 发送队列中有等待的发送协程,则直接将该发送协程携带的数据复制给接收指针
- 如果无等待中的发送队列,则从channel buffer中取出数据复制给接收指针
- 如果channel buffer为空,根据调用方是否需要阻塞:
- 不阻塞,直接返回接收失败
- 阻塞,新组装sudog,并将其加入recvq,此时接收数据的协程也出于阻塞状态
其他函数
// 根据索引值,寻找channel内数据的指针位置
func chanbuf(c *hchan, i uint) unsafe.Pointer {
return add(c.buf, uintptr(i)*uintptr(c.elemsize))
}
// 判断缓存channel是否已满
func full(c *hchan) bool {
if c.dataqsiz == 0 {
return c.recvq.first == nil
}
return c.qcount == c.dataqsiz // 已有数据量是否等于channel容量
}