When runtime.chansend is called to write data to chan, chansend checks whether there is a receiver in the chan object. If there is a receiver, send is called.

Send source code Analysis

Traditionally, because there is too much knowledge about other aspects of GO in the source code, this article only focuses on channel, so we must discard some of the code to make channel implementation easier to understand.

func send(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(a).skip int) {
             / /...
             // Where elem executes the memory address used by the receiver to receive chan data
            ifsg.elem ! =nil {
                    // Copy the sent data to ELEm, because EP and SG belong to two different coroutines (in memory space)
                    sendDirect(c.elemtype, sg, ep)
                    // Do not understand this sentence
                    sg.elem = nil
            }
            gp := sg.g
            unlockf()
            gp.param = unsafe.Pointer(sg)
            sg.success = true
            ifsg.releasetime ! =0 {
                    sg.releasetime = cputicks()
            }
            // Prepare the coroutine and push it to the coroutine queue. The coroutine queue is scheduled by the coroutine manager
            goready(gp, skip+1)}Copy the code

As you can see from this code:

  1. Send is responsible for copying data between two coroutines
  2. And the coroutine pushes onto the coroutine stack

Chanrecv source code interpretation

func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
	// ...
        // If a receiver exists, the recv function is called to push the coroutine corresponding to the receiver onto the coroutine stack
	ifsg := c.sendq.dequeue(); sg ! =nil {
		recv(c, sg, ep, func(a) { unlock(&c.lock) }, 3)
		return true.true
	}
        // If there is unsent data, then
	if c.qcount > 0 {
            // ...
	}

	if! block { unlock(&c.lock)return false.false
	}

	// If the above code does not execute, there is no sender yet, and a sender needs to be created
	gp := getg()
        // Create a sudog. Sudog can be understood as a coroutine state
	mysg := acquireSudog()
	mysg.releasetime = 0
	ift0 ! =0 {
		mysg.releasetime = - 1
	}
        // This coroutine is associated with a value called ep, which is the value we pass with <-
	mysg.elem = ep
	mysg.waitlink = nil
	gp.waiting = mysg
        // Associate coroutine state to a coroutine
	mysg.g = gp
	mysg.isSelect = false
	mysg.c = c
	gp.param = nil
        // Add to the receiver queue
	c.recvq.enqueue(mysg)
	
	ifmysg ! = 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
        // Free up the memory we used to create temporary variables
	releaseSudog(mysg)
	return true, success
}
Copy the code

The above source code differs from Chansend in that it creates a list of recipients, and then queues the recipients as follows:

  1. If there is a send queue, the data received by chan is sent directly through the RECV function
  2. If there is no send queue, the data received by chan is first saved on hchan.buf and the corresponding value of the hchan object is modified
  3. Create a Sudog object that records which coroutine it is going to execute on, along with some state data needed for execution, such as the value of the current associated chan, and the chan object itself
  4. After the sudog object is created, the sudog object is put into the RECVQ queue corresponding to the current Hchan object, because there is no sender at this point, so it needs to be cached
  5. Clean up the space occupied by sudog objects and return memory