The runtime/select.go source code is only 500+ lines

Select supports two types of sending and receiving operations: blocking and non-blocking. If multiple cases are ready, one of them is selected randomly, not in sequence. If there is no ready case, there is a default statement and the default statement is executed; No default statement, block until some case ready 4. Case in SELECT must be a channel operation 5. The default statement is always run

1. Select returns after timeout without waiting

func main() {
    c := boring("Joe")
    timeout := time.After(5 * time.Second)
    for {
        select {
        case s := <-c:
            fmt.Println(s)
        case <-timeout:
            fmt.Println("You talk too much.")
            return
        }
    }
}
Copy the code

2. Production, consumer communication When consumer consumption reaches a certain condition and no longer needs data, send a quit signal; The producer uses a SELECT statement, one case produces data, and the other case listens for quit messages. Upon receiving a stop signal from the consumer, the producer jumps out of the SELECT statement and stops producing data consumers

// Create quit channel
quit := make(chan string)
// Start the producer goroutine
c := boring("Joe", quit)
// Read the result from the producer channel
for i := rand.Intn(10); i >= 0; I -- {fmt.Println(<-c)} // Use quit channel to tell producers to stop production quit <- "Bye!" fmt.Printf("Joe says: %q\n", <-quit)Copy the code

producers

select {
case c <- fmt.Sprintf("%s: %d", msg, i):
    // do nothing
case <-quit:
    cleanup()
    quit <- "See you!"
    return
}
Copy the code

If there is no data, do not want to continue to block. Add default statement, if there is data, return, no default statement, for example, if there is an error, the data will be inserted into the C channel, if there is a select, then catch the data. If no, go to the default logic. I just want to see if there are Any Errs, I don’t care how many

select {
case m <- c:
    // print something
case default:
    // print something else
}
Copy the code

Specific source code analysis

The case in SELECT is represented by runtime.scase

type scase struct {
	c           *hchan         // All operations except default are channel operations, so we need a channel variable to store channel information
	elem        unsafe.Pointer // data element
	kind        uint16// Case type default statement is caseDefault, receive channel is caseRecv, send channel is caseSend
	pc          uintptr // race pc (for race detector / msan)
	releasetime int64
}
Copy the code

The channel type code is defined as follows

const (
	caseNil = iota
	caseRecv
	caseSend
	caseDefault
)
Copy the code

Compiler code generation in the middle period according to select different optimize the control statements, in the case of a process that occurs in the CMD/compile/internal/gc walkselectcases function, For example, if the select has no case, the current Goroutine will be suspended, and the processor will be given away, causing the Goroutine to go into permanent sleep and cannot be awakened. Call the runtime. selectGo method, which does all the work in this method, passing in an array of scase arguments and passing out a randomly selected scase subscript of Ready, as shown below

func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) {
	if debugSelect {
		print("select: cas0=", cas0, "\n")}...Copy the code

The method is divided into three parts

Initialize case pollOrder and lockOrder

Use runtime.fastrandn to shuffle case access orderCopy the code
	// generate permuted order
	for i := 1; i < ncases; i++ {
		j := fastrandn(uint32(i + 1))
		pollorder[i] = pollorder[j]
		pollorder[j] = uint16(i)
	}
Copy the code

The following step 2 access is out of order, which is why multiple cases ready execute one randomly

loop:
	// pass 1 - look for something already waiting
	var dfli int
	var dfl *scase
	var casi int
	var cas *scase
	var recvOK bool
	for i := 0; i < ncases; i++ {
		casi = int(pollorder[i])// PollOrder returns the index of the case array, which has been scrambled so that a random case is found. If ready, execute this case directly
		cas = &scases[casi]
		c = cas.c

		switch cas.kind {
		case caseNil:
			continue

		case caseRecv:
			sg = c.sendq.dequeue()
			ifsg ! = nil {Copy the code

LockOrder specifically uses a simple heap sort to sort the channel address. In order to sequentially lock the channel when reading data, and to prevent multiple locks on the same channel

	for i := 0; i < ncases; i++ {
		j := i
		// Start with the pollorder to permute cases on the same channel.
		c := scases[pollorder[i]].c
		for j > 0 && scases[lockorder[(j-1) /2]].c.sortkey() < c.sortkey() {
			k := (j - 1) / 2
			lockorder[j] = lockorder[k]
			j = k
		}
		lockorder[j] = pollorder[i]
	}
	for i := ncases - 1; i >= 0; i-- {
		o := lockorder[i]
		c := scases[o].c
		lockorder[i] = lockorder[0]
		j: =0
		for {
			k := j*2 + 1
			if k >= i {
				break
			}
			if k+1 < i && scases[lockorder[k]].c.sortkey() < scases[lockorder[k+1]].c.sortkey() {
				k++
			}
			if c.sortkey() < scases[lockorder[k]].c.sortkey() {
				lockorder[j] = lockorder[k]
				j = k
				continue
			}
			break
		}
		lockorder[j] = o
	}
Copy the code

Two. Main loop

1. The case is iterated in the for loop to see if it is ready. If it is ready, skip to the processing part and the process ends

	// lock all the channels involved in the select
	sellock(scases, lockorder)// Lock all channels before reading or writing them

	var (
		gp     *g
		sg     *sudog
		c      *hchan
		k      *scase
		sglist *sudog
		sgnext *sudog
		qp     unsafe.Pointer
		nextp  **sudog
	)
loop:
	// pass 1 - look for something already waiting
	var dfli int
	var dfl *scase
	var casi int
	var cas *scase
	var recvOK bool
	for i := 0; i < ncases; i++ {
		casi = int(pollorder[i])// Find a random case
		cas = &scases[casi]
		c = cas.c

		switch cas.kind {
		case caseNil:
			continue// If it is empty, skip it

		case caseRecv:
			sg = c.sendq.dequeue()
			ifsg ! = nil { goto recv// If a channel has a goroutine waiting in the queue, it will be sent directly to recV. This is the same as the previous implementation of channel. The channel is buff, the goroutine of the queue is copied to the current location of the buff, and the goroutine of the queue is released. See channel source code analysis of the article
			}
			if c.qcount > 0 {
				goto bufrecv// If there is no waiting column, the buff has a value and copies directly from the buff
			}
			ifc.closed ! =0 {// There is no data, if closed, do some work to clear data
				goto rclose
			}

		case caseSend:
			if raceenabled {
				racereadpc(c.raceaddr(), cas.pc, chansendpc)
			}
			ifc.closed ! =0 {// Send channel, close direct panic
				goto sclose
			}
			sg = c.recvq.dequeue()// Copy directly to the waiting receive channel and wake up
			ifsg ! = nil { goto send }if c.qcount < c.dataqsiz {// There is no waiting channel, there is room in the buff
				goto bufsend
			}

		case caseDefault:
			dfli = casi
			dfl = cas
		}
	}

	ifdfl ! = nil {// If default does, execute the default statement without blocking
		selunlock(scases, lockorder)// Unlock all channels
		casi = dfli
		cas = dfl
		goto retc
	}

Copy the code

If there is a ready case, execute it at random. If there is no default case, execute the default statement

The current goroutine is attached to the wait queue of each channel, waiting to wake up

// pass 2 - enqueue on all chans
	gp = getg()
	ifgp.waiting ! = nil {throw("gp.waiting ! = nil")
	}
	nextp = &gp.waiting
	for _, casei := range lockorder {
		casi = int(casei)
		cas = &scases[casi]
		if cas.kind == caseNil {
			continue
		}
		c = cas.c
		sg := acquireSudog()// Encapsulate the Goroutine as suGOD and place it on the channel wait queue
		sg.g = gp
		sg.isSelect = true
		// No stack splits between assigning elem and enqueuing
		// sg on gp.waiting where copystack can find it.
		sg.elem = cas.elem
		sg.releasetime = 0
		ift0 ! =0 {
			sg.releasetime = -1
		}
		sg.c = c
		// Construct waiting list in lock order.
		*nextp = sg
		nextp = &sg.waitlink

		switch cas.kind {
		case caseRecv:
			c.recvq.enqueue(sg)// Put it in the wait queue

		case caseSend:
			c.sendq.enqueue(sg)
		}
	}

	// wait for someone to wake us up
	gp.param = nil
	gopark(selparkcommit, nil, waitReasonSelect, traceEvGoBlockSelect, 1)// Goroutine hangs, waiting to wake up
	gp.activeStackChans = false

	sellock(scases, lockorder)

	gp.selectDone = 0
	sg = (*sudog)(gp.param)
	gp.param = nil

Copy the code

When a channel is ready, the current Goroutine will be awakened by the scheduler and returned to the current case. In other cases, the channel queue removes the Goroutine and no longer waits

	// pass 3 - dequeue from unsuccessful chans
	// otherwise they stack up on quiet channels
	// record the successful case, if any.
	// We singly-linked up the SudoGs in lock order.
	casi = -1
	cas = nil
	sglist = gp.waiting
	// Clear all elem before unlinking from gp.waiting.
	forsg1 := gp.waiting; sg1 ! = nil; sg1 = sg1.waitlink { sg1.isSelect =false
		sg1.elem = nil
		sg1.c = nil
	}
	gp.waiting = nil

	for _, casei := range lockorder {
		k = &scases[casei]
		if k.kind == caseNil {
			continue
		}
		if sglist.releasetime > 0 {
			k.releasetime = sglist.releasetime
		}
		if sg == sglist {
			// sg has already been dequeued by the G that woke us up.
			casi = int(casei)// Find the awakened case
			cas = k
		} else {
			c = k.c
			if k.kind == caseSend {// All other cases are removed from the waiting queue of the channel. This goroutine is already waiting for data in the upper case, no more cases
				c.sendq.dequeueSudoG(sglist)
			} else {
				c.recvq.dequeueSudoG(sglist)
			}
		}
		sgnext = sglist.waitlink
		sglist.waitlink = nil
		releaseSudog(sglist)
		sglist = sgnext
	}

	if cas == nil {
		// We can wake up with gp.param == nil (so cas == nil)
		// when a channel involved in the select has been closed.
		// It is easiest to loop and re-run the operation;
		// we'll see that it's now closed.
		// Maybe some day we can signal the close explicitly,
		// but we'd have to distinguish close-on-reader from close-on-writer.
		// It's easiest not to duplicate the code and just recheck above.
		// We know that something closed, and things never un-close,
		// so we won't block again.
		goto loop
	}

	c = cas.c
...
Copy the code

Select keyword is a special control structure of Go language. Its implementation principle is complex and requires the full cooperation of compiler and runtime function

Reference draveness. Me/golang/docs…