The Go SELECT statement is a special statement that can only be used by channL to send and receive messages. Block the current Groutine when there is no case statement in the SELECT. So, one could also say that select is used to block listening on goroutine. Select is an I/O multiplexing mechanism provided by Golang at the language level, which is specifically used to check if multiple channels are ready: readable or writable.

All of the above statements are correct.

I/O multiplexing

Let’s review what I/O multiplexing is.

Normal multithreaded (or process) I/O

With each incoming process, a connection is established and then blocked until a response is received. The downside of this approach is obvious: the system needs to create and maintain additional threads or processes. Most of the time, most of the blocked threads or processes are in a waiting state, with only a few receiving and processing the response while the rest are waiting. The system also needs to do a lot of extra thread or process management.

To deal with these redundant threads or processes in the diagram, we have “I/O multiplexing “.

I/O multiplexing

Each thread or process is registered in the device, then blocked, and only one thread is transported. When the registered thread or process is ready for data, the device will get corresponding data according to the registered information. From the beginning to the end, the kernel only uses the yellow thread in the figure, and there is no need to manage additional threads or processes, which improves efficiency.

Select component structure

The implementation of the select statement is actually composed of two parts: the case statement and the execution function. Source address: / go/SRC/runtime/select. Go

Each case statement abstracts the following structures separately:

type scase struct {
    c           *hchan         // chan
    elem        unsafe.Pointer // Read/write buffer address
    kind        uint16   // The type of the case statement is default, pass write data (channel <-) or value read data (< -channel)
    pc          uintptr // race pc (for race detector / msan)
    releasetime int64
}
Copy the code

The structure can be represented as follows:

hchan
scase

Then executing the select statement is essentially calling the func selectGo (cas0 *scase, Order0 *uint16, ncases int) (int, bool) function.

Func selectGo (cas0 *scase, Order0 *uint16, ncases int) (int, bool)

  • Cas0 is the structure abstracted from the case statement mentioned abovescaseThe address of the first element of the array
  • Order0 is a buffer twice the length of cas0 array that holds the scase random sequence PollORDER and the scase channel address sequence lockOrder.
  • Nncases saidscaseArray length

Selectgo returns the index of the selected SCase (which matches the ordinal position of its respective SELECT {recv, send, default} calls). Also, if the SCASE selected is a receive operation (RECV), returns whether the value was received.

Who is responsible for calling func selectGo (cas0 *scase, Order0 *uint16, ncases int) (int, bool)?

Select func rSELECT ([]runtimeSelect) (chosen int, recvOK bool) in /reflect/value.go The implementation of this function is in the func reflect_rselect(cases []runtimeSelect) (int, bool) function in /runtime/select.go:

func reflect_rselect(cases []runtimeSelect) (int.bool) { 
    // Blocks the current Groutine if the CASES statement is empty
    if len(cases) == 0 {
        block()
    }
    // Instantiate the case structure
    sel := make([]scase, len(cases))
    order := make([]uint16.2*len(cases))
    for i := range cases {
        rc := &cases[i]
        switch rc.dir {
        case selectDefault:
            sel[i] = scase{kind: caseDefault}
        case selectSend:
            sel[i] = scase{kind: caseSend, c: rc.ch, elem: rc.val}
        case selectRecv:
            sel[i] = scase{kind: caseRecv, c: rc.ch, elem: rc.val}
        }
        if raceenabled || msanenabled {
            selectsetpc(&sel[i])
        }
    }
    return selectgo(&sel[0], &order[0].len(cases))
}
Copy the code

Func rSELECT ([]runtimeSelect) (chosen int, recvOK bool)? In /refect/value.go, there is a func Select(cases []SelectCase) (chosen Int, recv value, recvOK bool), which calls rSELECT, And returns the return value of the select statement in the final Go.

The call stack for the above three functions is as follows:

  • func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool)
  • func rselect([]runtimeSelect) (chosen int, recvOK bool)
  • func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool)

The return values and arguments of these three functions are similar. The argument is passed in as a case statement, and the return value returns the selected case statement. So who calls func Select(chosen Int, RecV Value, recvOK bool)? We can simply think of it as a system. Here’s a simple picture:

The first two functions Select and rSELECT call the next function with a simple initialization parameter. The real core function of select is implemented in the last function func selectGo (cas0 *scase, Order0 *uint16, ncases int) (int, bool).

What does the selectGo function do

Scrambles the order of incoming case constructs

Lock all channels in it

Iterate over all channels to see if they are readable or writable

If the channels are readable or writable, all the channels are unlocked and the corresponding channel data is returned

If no channel is readable or writable, but there is a default statement, the same as above: return the scase corresponding to the default statement and unlock all channels.

If there are no channels to read or write, and no default statements, the currently running Groutine is blocked and added to the queue for all current channels.

Then unlock all channels and wait to be woken up.

If a channel is readable or writable, wake up and lock all channels again.

Traverse all channels to find the corresponding channel and G, wake up G, and remove unsuccessful G from the wait queue of all channels.

If the corresponding scase value is not empty, the desired value is returned and all channels are unlocked

If the corresponding SCase is empty, the process is repeated.

The relationship between select and channel

Think about what select and channel do, I think it’s the same thing as multiplexing, right

For more exciting content, please follow my wechat official accountInternet Technology NestOr add wechat to discuss and exchange:

References:

  • My.oschina.net/renhc/blog/…
  • Blog.csdn.net/xd_rbt_/art…
  • Blog.csdn.net/qq_34199383…
  • Blog.csdn.net/wangxindong…
  • Draveness. Me/golang – sele…
  • Studygolang.com/articles/18…