This is a creation in Article, where the information may have evolved or changed.
Select syntax summarizes each case for Select if there is an already prepared case for Chan Read and write operations, or if no defualt statement is executed, and if none is blocking the current goroutine until a certain Chan is ready for readable or writable , and exit after completing the corresponding case.
Memory layout for Select
After understanding the implementation of Chanel, there is a question about the syntax of SELECT, how select Implements Multiplexing, and why it is not blocked in the first channel operation, resulting in subsequent case execution. To solve the problem, the corresponding code looks at the functions of the runtime layer called by the Assembly, and finds that the SELECT syntax block is translated into the following procedure by the compiler.
Create select–> Register case–> perform select–> release select
select { case c1 <-1: // non-blocking case <-c2: // non-blocking default: // will do this }
runtime.newselectruntime.selectsendruntime.selectrecvruntime.selectdefaultruntime.selectgo
The select is actually a hselect structure in which the registered case is placed in the scase. Scase saves the Hchan with the current case operation. Pollorder points to the scase sequence number after the disorder. The address of the Hchan that corresponds to each case will be saved in Lockorder.
type hselect struct { tcase uint16 // total count of scase[] ncase uint16 // currently filled scase[] pollorder *uint16 // case poll order lockorder **hchan // channel lock order scase [1]scase // one per case (in order of appearance)}type scase struct { elem unsafe.Pointer // data element c *hchan // chan pc uintptr // return pc kind uint16 so uint16 // vararg of selected bool receivedp *bool // pointer to received bool (recv2) releasetime int64}
The select is finally [1]scase means that only one case space is saved in select, stating that select is just a header, and that all scase are saved in the Select, and the Scases is tcase. This is often seen in the go runtime implementation in the form of Head + continuous memory.
Implementation of SELECT
Select Create
The number of case is already known in the Newselect object, and the above space has been allocated.
func selectsize(size uintptr) uintptr { selsize := unsafe.Sizeof(hselect{}) + (size-1)*unsafe.Sizeof(hselect{}.scase[0]) + size*unsafe.Sizeof(*hselect{}.lockorder) + size*unsafe.Sizeof(*hselect{}.pollorder) return round(selsize, _Int64Align)}func newselect(sel *hselect, selsize int64, size int32) { if selsize != int64(selectsize(uintptr(size))) { print("runtime: bad select size ", selsize, ", want ", selectsize(uintptr(size)), "\n") throw("bad select size") } sel.tcase = uint16(size) sel.ncase = 0 sel.lockorder = (**hchan)(add(unsafe.Pointer(&sel.scase), uintptr(size)*unsafe.Sizeof(hselect{}.scase[0]))) sel.pollorder = (*uint16)(add(unsafe.Pointer(sel.lockorder), uintptr(size)*unsafe.Sizeof(*hselect{}.lockorder)))}
Register case
There are three types of case channel registrations selectsend selectrecv selectdefault , each corresponding to a different one. They are registered in the same way, are ncase+1, and then populate the relevant fields of the Scase array of the Scases field with the current index, mainly by populating the C and kind fields with the Chan and case types in case.
func selectsendImpl(sel *hselect, c *hchan, pc uintptr, elem unsafe.Pointer, so uintptr) { i := sel.ncase sel.ncase = i + 1 cas := (*scase)(add(unsafe.Pointer(&sel.scase), uintptr(i)*unsafe.Sizeof(sel.scase[0]))) cas.pc = pc cas.c = c cas.so = uint16(so) cas.kind = caseSend cas.elem = elem}
Select execution
Pollorder is the sequence number of the scase, and the random order is for the subsequent execution.
Lockorder saved all the address of the channel in the case, where the contiguous memory corresponding to the Lockorder is stacked according to the address size. Chan was ordered to go to the heavy, guaranteeing that all channel locks will not be re-locked when locked.
The entire Chanel is locked when the SELECT statement executes
The SELECT statement creates a select object that can be frequently allocated memory if placed in a for loop for long-term execution
The select execution process is summarized as follows:
The
- iterates through the scase to find the case that is ready through the ordinal of the Pollorder. Perform normal Chan read-write operations if any. The prepared case refers to the can not block the completion of the read-write Chan case, or read the closed Chan's case .
- If a case is not prepared, try to defualt it.
- If none of the above is present, the current G package is attached to the Scase list of all Chan, and is hung to SENDQ or RECVQ according to the type of Chan's operation.
- This g is awakened by some Chan, traversing scase to find the target case, discarding the current G in other Chan's wait, return.
Func Selectgoimpl (sel *hselect) (UIntPtr, uint16) {//Pollorder unordered fill ordinal//lockorder sort fill scase corresponding Hchan//via Lo Ckorder Traverse each chan lockout sellock (SEL) Loop://Follow the Pollorder sequence scase see if there is a case ready for I: = 0; i < int (sel.ncase); i++ {cas = &scases[pollorder[i]] Switch cas.kind {case Caserecv:case casesend:c ASE CASEDEFAULT:DFL = cas}}//If Scase is not ready, try to execute defaut if DFL! = nil {selunlock (SE L) cas = DFL goto RETC}//If there are no available case to hang the current g to all case-corresponding Chan//waiting list SENDQ or recvq waiting to be awakened for I: = 0; i < int (sel.ncase); i++ {cas = &scases[pollorder[i]] c = cas.c sg: = Acquiresudog () switch Cas.kind { Case Caserecv:c.recvq.enqueue (SG) case casesend:c.sendq.enqueue (SG)}} gp.par am = Nil Gopark (selparkcommit, unsafe. Pointer (SEL), "Select", Traceevgoblockselect|futile, 2)//Wake up and lock again! Sellock (SEL) sg = (*sudog) (gp.param) Gp.param = nil//Wake up the Sudog of the current G is a saved list match for i: = Int (sglist) before the SG traversal ASE)-1; I >= 0; i--{k = &scases[pollorder[i]] if sg = = sglist {cas = k} else {//if not matched Retract the current G in this Chan queue c = k.c if K.kind = = casesend {C.sendq.dequeuesudog (sglist) } else {C.recvq.dequeuesudog (sglist)}} sgnext = Sglist.waitlink rele Asesudog (sglist) sglist = sgnext} selunlock (sel) goto Retcretc:return cas.pc, cas.so}
Reference articles
Select in Go Runtime
Go1.5 Source Code Analysis