此文是goroutine 调度系列 的chan
chan 接收和发送正好是反的逻辑,这里只分析了先收再发
收的时候如果拿不到数据,会创建一个sudog,把它添加到chan的等待接收队列上
发送方发送时如果检查到chan 接受队列有成员.也就是sudog,对响应的goroutine进行操作,把它设置成就绪状态(尝试添加到本地P中,如果不行就放到全局就绪g队列),修改接受对象的值
熟悉go的都知道chan 有2种写法:
1 something:<- ch
2 something,ok <- ch
编译器帮我们改写成runtime下的
func chanrecv1(c *hchan, elem unsafe.Pointer) {
chanrecv(c, elem, true)
}
func chanrecv2(c *hchan, elem unsafe.Pointer) (received bool) {
_, received = chanrecv(c, elem, true)
return
}
真实入口
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
...
从此知道chan 如果是nil的话 receive会阻塞
if c == nil {
if !block {
return
}
gopark(nil, nil, "chan receive (nil chan)", traceEvGoStop, 2)
throw("unreachable")
}
,,,
mysg := acquireSudog()
...
mysg.g = gp
...
c.recvq.enqueue(mysg)
...
goparkunlock(&c.lock, "chan receive", traceEvGoBlockRecv, 3)
...
}
func goparkunlock(lock *mutex, reason string, traceEv byte, traceskip int) {
m放弃g,m主动触发调度
gopark(parkunlock_c, unsafe.Pointer(lock), reason, traceEv, traceskip)
}
接下来看一下发送
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
同样,如果chan 是nil 会阻塞
if c == nil {
if !block {
return false
}
gopark(nil, nil, "chan send (nil chan)", traceEvGoStop, 2)
throw("unreachable")
}
...
if sg := c.recvq.dequeue(); sg != nil {
send(c, sg, ep, func() { unlock(&c.lock) }, 3)
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
}
...
goready(gp, skip+1)
}
func sendDirect(t *_type, sg *sudog, src unsafe.Pointer) {
...
memmove(dst, src, t.size)
...
}
func goready(gp *g, traceskip int) {
systemstack(func() {
ready(gp, traceskip, true)
})
}
func ready(gp *g, traceskip int, next bool) {
...
设置就绪状态
casgstatus(gp, _Gwaiting, _Grunnable)
添加到p中,尝试放当前线程的p中如果不行那就放入全局就绪g队列中
runqput(_g_.m.p.ptr(), gp, next)
...
}