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…