It starts with pure source code interpretation and ends with string.
recv
/ / 1
v, ok := <- ch
/ / 2
v := <- ch
Copy the code
Chanrecv (ch, elem, true)
// entry points for <- c from compiled code
func chanrecv1(c *hchan, elem unsafe.Pointer) {
chanrecv(c, elem, true)}// received is the returned OK
func chanrecv2(c *hchan, elem unsafe.Pointer) (received bool) {
_, received = chanrecv(c, elem, true)
return
}
Copy the code
Both functions pass blocks that are true and block, regardless of the case of block=false.
Part I
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
// debug code.if c == nil {
if! block {return
}
gopark(nil.nil, waitReasonChanReceiveNilChan, traceEvGoStop, 2)
throw("unreachable")}}Copy the code
Chan is nil. Just like send, reading from nil chan, the caller will block forever.
Part II
The skip. ! Block is not in our discussion at this time.
Part III,
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool){...// Set the lock on return.
lock(&c.lock)
// chan is null by close && chan
ifc.closed ! =0 && c.qcount == 0 {
if raceenabled {
raceacquire(c.raceaddr())
}
unlock(&c.lock)
ifep ! =nil {
typedmemclr(c.elemtype, ep)
}
return true.false}... }Copy the code
It’s very clear. Because now we’re finally starting to manipulate this BUF, lock it, and it’s a global lock, we’re either going to have to send/ RECV
Now chan close && Chan Empty, this chan has nothing left, what else can he do? Return false.
Part IV,
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool){...ifsg := c.sendq.dequeue(); sg ! =nil {
recv(c, sg, ep, func(a) { unlock(&c.lock) }, 3)
return true.true}... }Copy the code
If the waiting queue contains G, buF is full:
-
Unbuff:
Chan can’t store data. Now, since G needs to receive data, it can send G -> recv G
-
Buff:
Copy an element from the buF header directly to the recV acceptance address; And also insert the sender value at the end of the BUF.
Part V,
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool){...if c.qcount > 0 {
// Select the current element from the loop array.
qp := chanbuf(c, c.recvx)
if raceenabled {
raceacquire(qp)
racerelease(qp)
}
// ep is the data received by the receiver. If val < -ch, ep points to val
ifep ! =nil {
typedmemmove(c.elemtype, ep, qp)
}
// Remove the removed value
typedmemclr(c.elemtype, qp)
// Move on
c.recvx++
// RecV is at the end of buF
if c.recvx == c.dataqsiz {
c.recvx = 0
}
// The number of elements in the buf array is reduced by 1
c.qcount--
unlock(&c.lock)
return true.true
}
if! block { unlock(&c.lock)return false.false}... }Copy the code
c.qcount > 0
instructionsbuf
And the data, for thebuf chan
.- Coming down from the previous section, it says there’s no sender waiting.
- from
buf
To return an element and place the element inbuf
Removed,recvx++, qcount--
- Unlock this unlock is a global lock, and
chansend
Public, which also ensures that there is only one operation inbuf
On 】
Part VI,
At this point, the situation looks like this:
buf
There is no datasender
sendG
There is no
That is, only G of the current recver is blocked:
-
Construct a sudog
-
The address for receiving data is saved
-
Join chan’s waiting receive queue
-
The current G is suspended
-
Then there are the various finishing touches after G is awakened.
I won’t show you the code.
conclusion
The recV process is divided into several stages:
unbuf chan
[Can’t cache data itself]- Have sent
G
, directly copy the data, done; - Did not send
G
, block the currentG
, join the receiving queue, suspend;
- Have sent
buf chan
:Buf data
From:buf
In therecx
The location data is copied to the receiver.Buf no data
: block currentrecv G
, join the receiving queue, suspend; [Copy data before sender if there is no sender]