接下来看看channel的读过程look:
// c channel内部指针结构
// eq 接受数据存储的地址
// block 是否阻塞接收
// selected 是否成功从channel中读取到数据
// received channel是否关闭
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
// raceenabled: don't need to check ep, as it is always on the stack
// or is new memory allocated by reflect.
if debugChan {
print("chanrecv: chan=", c, "\n")
}
// 判断cahnnel是否为nil 如果是nil的话 直接死锁 无法被唤醒
// 阻塞当前channel goroutine将不会被唤醒
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.
// 非阻塞 channel为空
if !block && empty(c) {
// After observing that the channel is not ready for receiving, we observe whether the
// channel is closed.
//
// Reordering of these checks could lead to incorrect behavior when racing with a close.
// For example, if the channel was open and not empty, was closed, and then drained,
// reordered reads could incorrectly indicate "open and empty". To prevent reordering,
// we use atomic loads for both checks, and rely on emptying and closing to happen in
// separate critical sections under the same lock. This assumption fails when closing
// an unbuffered channel with a blocked send, but that is an error condition anyway.
// 在非阻塞模式下 未关闭 直接返回
if atomic.Load(&c.closed) == 0 {
// Because a channel cannot be reopened, the later observation of the channel
// being not closed implies that it was also not closed at the moment of the
// first observation. We behave as if we observed the channel at that moment
// and report that the receive cannot proceed.
return
}
// The channel is irreversibly closed. Re-check whether the channel has any pending data
// to receive, which could have arrived between the empty and closed checks above.
// Sequential consistency is also required here, when racing with such a send.
// 如果channel关闭且为空 返回零值 接收失败
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.
// 这里通道没有关闭 取等待的发送的goroutine,就是写的时候等待被唤醒的goroutine
// 从阻塞的写协程队列中获取一个写goroutine
// 如果channel无缓冲区,直接读取写协程的元素,唤醒写goroutine
// 如果channel有缓冲区,直接读取缓冲区头部元素,并将写goroutine元素写入缓冲区尾部并唤醒写goroutine
// 解锁 返回
if sg := c.sendq.dequeue(); sg != nil {
// Found a waiting sender. If buffer is size 0, receive value
// directly from sender. Otherwise, receive from head of queue
// and add sender's value to the tail of the queue (both map to
// the same buffer slot because the queue is full).
recv(c, sg, ep, func() { unlock(&c.lock) }, 3)
return true, true
}
}
// 缓冲区中有数据 读取缓冲区数据
if c.qcount > 0 {
// Receive directly from queue
// 知道recvx的值 直接从buf中拿到相对应的数据就可以
qp := chanbuf(c, c.recvx)
if raceenabled {
racenotify(c, c.recvx, nil)
}
if ep != nil {
typedmemmove(c.elemtype, ep, qp)
}
// 拷贝数据
typedmemclr(c.elemtype, qp)
// 拿到数据之后自增recvx++
c.recvx++
// recvx == 当前channel的大小 直接 recvx == 0 形成一个闭环
// 就是变成一个环形数组
if c.recvx == c.dataqsiz {
c.recvx = 0
}
// 数量-1 解锁返回
c.qcount--
unlock(&c.lock)
return true, true
}
// 非阻塞模式下 channel 为空 直接返回
if !block {
unlock(&c.lock)
return false, false
}
// no sender available: block on this channel.
// 阻塞模式下等待接收数据 缓冲区没有元素可以读
// 构造sudog对象 完成指针指向操作
// 把封装好的sudog添加(挂起)到当前channel的阻塞读协程队列中
// 等待被唤醒
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
gp.waiting = mysg
mysg.g = gp
mysg.isSelect = false
mysg.c = c
gp.param = nil
c.recvq.enqueue(mysg)
// Signal to anyone trying to shrink our stack that we're about
// to park on a channel. The window between when this G's status
// changes and when we set gp.activeStackChans is not safe for
// stack shrinking.
gp.parkingOnChan.Store(true)
gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanReceive, ,,traceEvGoBlockRecv, 2)
// someone woke us up
// 如果被唤醒 则回收sudog 恢复goroutine状态
// 这里被唤醒其实就是有对应的元素写入
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读取的时候会死锁,挂起永远不会被唤醒。判断如果非阻塞channel为空,channel未关闭,直接返回,然后判断channel为读取成功,通道关闭。
接着上锁,如果当前通道已经关闭,缓冲区的已经没有数据直接释放锁,然后返回读取成功,通道已经关闭。如果通道不关闭,去写的snedq队列中去取等待被唤醒的goroutine队列去执行。如果是有缓冲区的channel则会去取头部的数据,并去唤醒正在等待唤醒的写协程执行将数据插入尾部,如果是无缓冲,直接取等待唤醒的写协程队列的数据并且唤醒写协程。
如果是有缓冲区并且缓冲区中有数据,先去拿到recvxd去获取到缓冲区的数据,然后进行recvx++然后判断recvx和datasiz是否相等,如果想等的话recvx = 0 形成一个闭环,就是环形数组。然后进行qcount--,释放锁,返回。
如果在阻塞缓冲区没有数据可以读的情况下,这个时候就要构造sudog,完成指针指向操作,封装完成sudog的然后挂起加入recvxq的队列中,等待唤醒。
这里其实就比较明确了。如果是读的时候发生阻塞,就去唤醒写的goroutine。如果是写的时候阻塞就去唤醒读的goroutine。
我这样理解不知道对不对,有错的地方希望指出,这样才有进步。