🧑?🏫 Go 中同步组件 Chan 的理解.
深入Chan底层源码进行分析Chan工作原理.
**学习比较枯草,但贵在坚持. 有关于源码的理解我都已经写在源码中的注释中了。**Go源码看起来要比Java舒服多了,仅仅只是对于环境上,哈哈哈,Java 看个Spring源码麻烦的一批.
🏷? 一、向Chan中发送数据.
1.1 总览全局:chan 发送数据的几个步骤.
- 第一种情况:
chan 关闭状态,直接panic ,表示不能向已经关闭状态的chan 读取数据. - 第二种情况:直接查看当前
chan 中等待接受数据的等待队列是否为空,如果不为空直接发送. - 第三种情况:查看当前与
chan 相关的环形队列是否为空,如果不为空则将当前要进行发送的数据放入进去. - 第四种情况:如果是非阻塞状态直接进行return,如果是阻塞状态,那么就将当前的
goroutine 添加到chan 关联的等待发送数据的等待队列中.
1.2 源码分析.
不管是通过x<-1 进行发送还是通过select 关键字发送,其最后调用runtime.chansend1 ,而该方法内部就是对函数chansend 的一次调用. 所以说chan 发送数据最终就是通过chansend 函数来进行发送.
func chansend1(c *hchan, elem unsafe.Pointer) {
chansend(c, elem, true, getcallerpc())
}
下面我们先看看整体的源码.
下面就是我们的源码分析, 这里我会把无关的源码和注释清理掉
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 !block && c.closed == 0 && full(c) {
return false
}
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 {
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
}
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)
atomic.Store8(&gp.parkingOnChan, 1)
gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceEvGoBlockSend, 2)
KeepAlive(ep)
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
}
🏷? 二、从Chan中读取数据.
2.1总览全局:从chan中接受数据
- 第一种情况:非阻塞,缓存为空,直接return.
- 第二种情况:chan关闭,且缓存为空 return.
- 第三种情况:如果chan上,待发送的队列队头不为nil,直接获取该对头上的数据.
- 第四种情况:如果环形队列
qcount 不为0,则直接从环形队列上进行获取. - 第五种情况:如果是非阻塞,那么直接return 如果是阻塞状态,那么将会进入等待接受数据的阻塞等待队列.
2.2 源码分析.
Go 中通过chan 发送数据,最终都会调用chanrecv 函数来完成发送. 下面的代码我会删掉无用代码以及无用注释.
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
if c == nil {
if !block {
return
}
gopark(nil, nil, waitReasonChanReceiveNilChan, traceEvGoStop, 2)
throw("unreachable")
}
if !block && empty(c) {
if atomic.Load(&c.closed) == 0 {
return
}
if empty(c) {
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 && c.qcount == 0 {
if raceenabled {
raceacquire(c.raceaddr())
}
unlock(&c.lock)
if ep != nil {
typedmemclr(c.elemtype, ep)
}
return true, false
}
if sg := c.sendq.dequeue(); sg != nil {
recv(c, sg, ep, func() { unlock(&c.lock) }, 3)
return true, true
}
if c.qcount > 0 {
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
}
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)
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
}
欢迎关注公众号:小马教你写Bug. 欢迎关注哔哩哔哩:LaoMaii. 欢迎关注我的个人网站
|