1. 数据结构
hchan
type hchan struct {
qcount uint // channel内元素的数量
dataqsiz uint // channel的容量
buf unsafe.Pointer // channel中元素队列(环形数组)
elemsize uint16 // channel中元素类型大小
closed uint32 // channel关闭标识
elemtype *_type // channel中元素类型
sendx uint // channel写入元素的index
recvx uint // channel读取元素的index
recvq waitq // channel阻塞读队列
sendq waitq // channel阻塞写队列
lock mutex // channel锁
}
waitq
type waitq struct {
first *sudog
last *sudog
}
就是两个指针,指向的是队列头部和尾部
sudog
type sudog struct {
g *g
next *sudog
prev *sudog
elem unsafe.Pointer
acquiretime int64
releasetime int64
ticket uint32
isSelect bool
success bool
parent *sudog // semaRoot binary tree
waitlink *sudog // g.waiting list or semaRoot
waittail *sudog // semaRoot
c *hchan // channel
}
-
g *g
:指向相关的 Goroutine。 -
next *sudog
和prev *sudog
:用于构成一个链表,用于管理等待在同一个 channel 上的多个 Goroutine。 -
elem unsafe.Pointer
:与等待的事件相关的数据元素。对于 channel,它可能指向 channel 上的数据;对于 select 语句,它可能指向 select case。 -
acquiretime int64
和releasetime int64
:用于记录 Goroutine 获取锁的时间和释放锁的时间。 -
ticket uint32
:用于竞争获取锁时的票据,类似于一个简单的等待队列。 -
isSelect bool
:指示 Goroutine 是否参与了 select 操作。 -
success bool
:用于标记通信是否成功。如果为 true,表示 Goroutine 因为成功接收到值而被唤醒;如果为 false,表示 Goroutine 因为 channel 被关闭而被唤醒。 -
parent *sudog
、waitlink *sudog
和waittail *sudog
:用于构建 Goroutine 等待队列的二叉树结构。在 channel 操作中,它们通常与 channel 的等待列表相关联。 -
c *hchan
:指向相关的 channel。
它允许 Goroutine 在等待 channel 上的发送或接收操作时,以一种高效的方式挂起和恢复,同时支持与 select 语句的集成。通过这个结构体,Go 语言实现了高效的并发编程模型。
makechan
func makechan(t *chantype, size int) *hchan {
elem := t.elem
if elem.size >= 1<<16 {
throw("makechan: invalid channel element type")
}
if hchanSize%maxAlign != 0 || elem.align > maxAlign {
throw("makechan: bad alignment")
}
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 {
case mem == 0:
// Queue or element size is zero.
c = (*hchan)(mallocgc(hchanSize, nil, true))
// Race detector uses this location for synchronization.
c.buf = c.raceaddr()
case elem.ptrdata == 0:
// Elements do not contain pointers.
// Allocate hchan and buf in one call.
c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
c.buf = add(unsafe.Pointer(c), hchanSize)
default:
// Elements contain pointers.
c = new(hchan)
c.buf = mallocgc(mem, elem, true)
}
c.elemsize = uint16(elem.size)
c.elemtype = elem
c.dataqsiz = uint(size)
lockInit(&c.lock, lockRankHchan)
if debugChan {
print("makechan: chan=", c, "; elemsize=", elem.size, "; dataqsiz=", size, "\n")
}
return c
}
1. 检查元素类型的大小是否合法,
2. 检查 hchan
结构体的大小是否符合对齐要求,如果不符合,则抛出异常。
3. 判断申请内存空间大小是否越界,mem 大小为 element 类型大小与 element 个数相乘后得到,仅当无缓冲型 channel 时,因个数为 0 导致大小为 0;
4. 根据类型,初始 channel,分为 无缓冲型、有缓冲元素为 struct 型、有缓冲元素为 pointer 型 channel;
5. 倘若为无缓冲型,则仅申请一个大小为默认值 96 的空间;
6. 如若有缓冲的 struct 型,则一次性分配好 96 + mem 大小的空间,并且调整 chan 的 buf 指向 mem 的起始位置;
7. 倘若为有缓冲的 pointer 型,则分别申请 chan 和 buf 的空间,两者无需连续;
8. 对 channel 的其余字段进行初始化,包括元素类型大小、元素类型、容量以及锁的初始化.
2.读写流程
写流程:
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
if c == nil {
if !block {
return false
}
gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2)
throw("unreachable")
}
if debugChan {
print("chansend: chan=", c, "\n")
}
if raceenabled {
racereadpc(c.raceaddr(), callerpc, abi.FuncPCABIInternal(chansend))
}
if !block && c.closed == 0 && full(c) {
return false
}
var t0 int64
if blockprofilerate > 0 {
t0 = cputicks()
}
lock(&c.lock)
if c.closed != 0 {
unlock(&c.lock)
panic(plainError("send on closed channel"))
}
if sg := c.recvq.dequeue(); sg != nil {
send(c, sg, ep, func() { unlock(&c.lock) }, 3)
return true
}
if c.qcount < c.dataqsiz {
// Space is available in the channel buffer. Enqueue the element to send.
qp := chanbuf(c, c.sendx)
if raceenabled {
racenotify(c, c.sendx, nil)
}
typedmemmove(c.elemtype, qp, ep)
c.sendx++
if c.sendx == c.dataqsiz {
c.sendx = 0
}
c.qcount++
unlock(&c.lock)
return true
}
if !block {
unlock(&c.lock)
return false
}
// Block on the channel. Some receiver will complete our operation for us.
gp := getg()
mysg := acquireSudog()
mysg.releasetime = 0
if t0 != 0 {
mysg.releasetime = -1
}
// No stack splits between assigning elem and enqueuing mysg
// on gp.waiting where copystack can find it.
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)
atomic.Store8(&gp.parkingOnChan, 1)
gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceEvGoBlockSend, 2)
KeepAlive(ep)
// someone woke us up.
if mysg != gp.waiting {
throw("G waiting list is corrupted")
}
gp.waiting = nil
gp.activeStackChans = false
closed := !mysg.success
gp.param = nil
if mysg.releasetime > 0 {
blockevent(mysg.releasetime-t0, 2)
}
mysg.c = nil
releaseSudog(mysg)
if closed {
if c.closed == 0 {
throw("chansend: spurious wakeup")
}
panic(plainError("send on closed channel"))
}
return true
}
- 首先,函数检查是否有空闲的 channel 缓冲区,如果有,则直接将元素放入缓冲区,并返回 true。
- 如果缓冲区已满,且不允许阻塞发送,则立即返回 false。
- 如果允许阻塞发送,则函数会创建一个
sudog
结构体,表示等待发送的 goroutine,并将其加入到 channel 的发送队列中。 - 然后,当前 goroutine 被挂起(通过
gopark
函数),等待其他 goroutine 接收数据或 channel 关闭。 - 当有接收者接收到数据或者 channel 关闭时,阻塞的 goroutine 被唤醒,继续执行。
- 如果 channel 已关闭但仍有发送者,则会抛出异常。
(用小徐老师的两张图)
读流程:
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
if debugChan {
print("chanrecv: chan=", c, "\n")
}
if c == nil {
if !block {
return
}
gopark(nil, nil, waitReasonChanReceiveNilChan, traceEvGoStop, 2)
throw("unreachable")
}
// Fast path: check for failed non-blocking operation without acquiring the lock.
if !block && empty(c) {
if atomic.Load(&c.closed) == 0 {
return
}
if empty(c) {
// The channel is irreversibly closed and empty.
if raceenabled {
raceacquire(c.raceaddr())
}
if ep != nil {
typedmemclr(c.elemtype, ep)
}
return true, false
}
}
var t0 int64
if blockprofilerate > 0 {
t0 = cputicks()
}
lock(&c.lock)
if c.closed != 0 {
if c.qcount == 0 {
if raceenabled {
raceacquire(c.raceaddr())
}
unlock(&c.lock)
if ep != nil {
typedmemclr(c.elemtype, ep)
}
return true, false
}
// The channel has been closed, but the channel's buffer have data.
} else {
// Just found waiting sender with not closed.
if sg := c.sendq.dequeue(); sg != nil {
recv(c, sg, ep, func() { unlock(&c.lock) }, 3)
return true, true
}
}
if c.qcount > 0 {
// Receive directly from queue
qp := chanbuf(c, c.recvx)
if raceenabled {
racenotify(c, c.recvx, nil)
}
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
}
// no sender available: block on this channel.
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)
atomic.Store8(&gp.parkingOnChan, 1)
gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanReceive, traceEvGoBlockRecv, 2)
// someone woke us up
if mysg != gp.waiting {
throw("G waiting list is corrupted")
}
gp.waiting = nil
gp.activeStackChans = false
if mysg.releasetime > 0 {
blockevent(mysg.releasetime-t0, 2)
}
success := mysg.success
gp.param = nil
mysg.c = nil
releaseSudog(mysg)
return true, success
}
- 首先,函数检查是否有待接收的元素,如果有,则直接从 channel 中获取元素,并返回 true。
- 如果没有待接收的元素,且不允许阻塞接收,则立即返回 false。
- 如果允许阻塞接收,则函数会创建一个
sudog
结构体,表示等待接收的 goroutine,并将其加入到 channel 的接收队列中。 - 然后,当前 goroutine 被挂起(通过
gopark
函数),等待其他 goroutine 发送数据或 channel 关闭。 - 当有发送者发送数据或者 channel 关闭时,阻塞的 goroutine 被唤醒,继续执行。
- 如果 channel 已关闭且为空,则函数返回 true 表示成功接收到数据,并返回 false 表示 channel 已关闭且为空。
- 如果 channel 已关闭但仍有待接收的元素,则从队列中接收数据并返回 true。
- 如果 channel 仍有缓冲区,则直接从缓冲区中接收数据并返回 true。
- 如果 channel 为空且没有其他发送者,则阻塞的 goroutine 被挂起,等待其他 goroutine 发送数据或 channel 关闭。
- 当有其他 goroutine 发送数据或 channel 关闭时,阻塞的 goroutine 被唤醒,继续执行并返回接收是否成功的状态。