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 > 0instructionsbufAnd the data, for thebuf chan.
  • Coming down from the previous section, it says there’s no sender waiting.
  • frombufTo return an element and place the element inbufRemoved,recvx++, qcount--
  • Unlock this unlock is a global lock, andchansendPublic, which also ensures that there is only one operation inbufOn 】

Part VI,

At this point, the situation looks like this:

  • bufThere is no data
  • sendersendGThere is no

That is, only G of the current recver is blocked:

  1. Construct a sudog

  2. The address for receiving data is saved

  3. Join chan’s waiting receive queue

  4. The current G is suspended

  5. 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 sentG, directly copy the data, done;
    • Did not sendG, block the currentG, join the receiving queue, suspend;
  • buf chan:
    • Buf dataFrom:bufIn therecxThe 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]