4. 读取chan
由汇编可以看出,读取一个chan,转化为对runtime.chanrecv1()的调用。如果采用两个返回值方式,则转化为runtime.chanrecv2()调用,区别在于返回一个bool值,标识是否接收到了数据。
- 参数1,chan本身。
- 参数2,接收读取数据的变量。
var var_chan_1 = make(chan int)
var_read = <-var_chan_1
0x00c7 00199 (channel.go:30) MOVQ $0, ""..autotmp_6+64(SP)
0x00d0 00208 (channel.go:30) MOVQ "".var_chan_1+96(SP), AX
0x00d5 00213 (channel.go:30) MOVQ AX, (SP)
0x00d9 00217 (channel.go:30) LEAQ ""..autotmp_6+64(SP), AX
0x00de 00222 (channel.go:30) MOVQ AX, 8(SP)
0x00e3 00227 (channel.go:30) CALL runtime.chanrecv1(SB)
0x00e8 00232 (channel.go:30) MOVQ ""..autotmp_6+64(SP), AX
0x00ed 00237 (channel.go:30) MOVQ AX, "".var_read+48(SP)
4.1 runtime.chanrecv1()
runtime.chanrecv1()是对 chanrecv()的封装。参数true表示阻塞调用。
// entry points for <- c from compiled code
//go:nosplit
func chanrecv1(c *hchan, elem unsafe.Pointer) {
chanrecv(c, elem, true)
}
//go:nosplit
func chanrecv2(c *hchan, elem unsafe.Pointer) (received bool) {
_, received = chanrecv(c, elem, true)
return
}
- 如果chan是nil
1)阻塞调用,则panic。
2)非阻塞调用则直接返回。 - 对于
非阻塞调用,进行快速判断是否有数据可读(避免对chan加锁)。- chan未被关闭.
- 循环队列(
dataqsiz长度为0,发送队列sendq为空。或者 循环队列dataqsiz不为空,但是队列中数据长度qcount为0.
- 对chan加锁。
- chan已被关闭,且没有数据可读取,直接返回.
如果此时chan仍未关闭,并且数据队列长度为0,则将接收返回值的对象清空。返回接收结束(true-接收成功,false-没有接收到数据)。 - 队列已满
如果等待发送队列sendq不空,则直接将发送方的数据copy到接收对象,返回。 - 队列未满
如果队列qcount中数据不为空,则从队列中取出数据。返回。 - 无数据可读
非阻塞调用,直接放回。
如果队列qcount中没数据,待发送队列sendq也为空,并且非阻塞调用,直接返回。阻塞调用,阻塞等待
创建一个sudog对象,放在接收等待队列recvq中。调用gopark()则设置抢占标记,并调用park_m()将G与M解除绑定,然后触发调度器调用schedule(),将当前goroutine挂起。
// chanrecv receives on channel c and writes the received data to ep.
// ep may be nil, in which case received data is ignored.
// If block == false and no elements are available, returns (false, false).
// Otherwise, if c is closed, zeros *ep and returns (true, false).
// Otherwise, fills in *ep with an element and returns (true, true).
// A non-nil ep must point to the heap or the caller's stack.
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 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.
//
// After observing that the channel is not ready for receiving, we observe that the
// channel is not closed. Each of these observations is a single word-sized read
// (first c.sendq.first or c.qcount, and second c.closed).
// 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.
//
// The order of operations is important here: reversing the operations can lead to
// incorrect behavior when racing with a close.
if !block && (c.dataqsiz == 0 && c.sendq.first == nil ||
c.dataqsiz > 0 && atomic.Loaduint(&c.qcount) == 0) &&
atomic.Load(&c.closed) == 0 {
return
}
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 {
// 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
qp := chanbuf(c, c.recvx)
if raceenabled {
raceacquire(qp)
racerelease(qp)
}
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
}
// 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)
goparkunlock(&c.lock, waitReasonChanReceive, traceEvGoBlockRecv, 3)
// someone woke us up
if mysg != gp.waiting {
throw("G waiting list is corrupted")
}
gp.waiting = nil
if mysg.releasetime > 0 {
blockevent(mysg.releasetime-t0, 2)
}
closed := gp.param == nil
gp.param = nil
mysg.c = nil
releaseSudog(mysg)
return true, !closed
}
4.2 recv()
- 如果是无缓冲chan,则直接将发送方的数据直接copy到接收对象上。
- 如果是带缓冲chan,将数据从接收缓存开始位置
recvxcopy到接收对象。将数据从发送方copy到队列。接收位置更新recvx++,如果超出缓存大小,重置到开始位置。重置发送位置为接收位置。 - 调用
goready(),设置goroutine状态为_Grunnable。
// recv processes a receive operation on a full channel c.
// There are 2 parts:
// 1) The value sent by the sender sg is put into the channel
// and the sender is woken up to go on its merry way.
// 2) The value received by the receiver (the current G) is
// written to ep.
// For synchronous channels, both values are the same.
// For asynchronous channels, the receiver gets its data from
// the channel buffer and the sender's data is put in the
// channel buffer.
// Channel c must be full and locked. recv unlocks c with unlockf.
// sg must already be dequeued from c.
// A non-nil ep must point to the heap or the caller's stack.
func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
if c.dataqsiz == 0 {
if raceenabled {
racesync(c, sg)
}
if ep != nil {
// copy data from sender
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.
qp := chanbuf(c, c.recvx)
if raceenabled {
raceacquire(qp)
racerelease(qp)
raceacquireg(sg.g, qp)
racereleaseg(sg.g, qp)
}
// copy data from queue to receiver
if ep != nil {
typedmemmove(c.elemtype, ep, qp)
}
// copy data from sender to queue
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
}
sg.elem = nil
gp := sg.g
unlockf()
gp.param = unsafe.Pointer(sg)
if sg.releasetime != 0 {
sg.releasetime = cputicks()
}
goready(gp, skip+1)
}
本文深入探讨了Go语言中通道(chan)的工作原理,包括如何通过runtime.chanrecv1()和runtime.chanrecv2()实现对chan的读取操作。文章详细分析了不同情况下接收数据的过程,如阻塞与非阻塞调用的区别,以及当chan为空或已关闭时的行为。
1万+

被折叠的 条评论
为什么被折叠?



