Go language channel and select principle

Source: Internet
Author: User
This is a creation in Article, where the information may have evolved or changed.

This article attempts to explain the implementation of channel and select in Go runtime, some of which are from gophercon2017. Go version is 1.8.3

Channel

The first section describes the use of the channel. The channel can be seen as a queue for communication between multiple goroutine, such as the following example, one goroutine sends the MSG, and the other MSG accepts the message. Channel is divided into buffer and without buffering, the difference is not very large, specifically please Google. See a simple example of how the channel is used.

package mainimport "fmt"func main() {    // Create a new channel with `make(chan val-type)`.    // Channels are typed by the values they convey.    messages := make(chan string)    // Send a value into a channel using the `channel <-`    // syntax. Here we send `"ping"`  to the `messages`    // channel we made above, from a new goroutine.    go func() { messages <- "ping" }()    // The `<-channel` syntax receives a value from the    // channel. Here we'll receive the `"ping"` message    // we sent above and print it out.    msg := <-messages    fmt.Println(msg)}

function Points of channel:

    1. Queue
    2. Blocking
    3. When one end is blocked, it can be awakened by the other side

We start with this 3-point function and talk about concrete implementations.

Channel structure

Annotations are marked with several important variables, which can be broadly divided into two functional units, one ring buffer for storing data, and one for the goroutine queue.

type hchan struct {    qcount   uint           // 当前队列中的元素个数    dataqsiz uint           // 缓冲队列的固定大小    buf      unsafe.Pointer // 缓冲数组    elemsize uint16    closed   uint32    elemtype *_type // element type    sendx    uint   // 下一次发送的 index    recvx    uint   // 下一次接收的 index    recvq    waitq  // 接受者队列    sendq    waitq  // 发送者队列    // lock protects all fields in hchan, as well as several    // fields in sudogs blocked on this channel.    //    // Do not change another G's status while holding this lock    // (in particular, do not ready a G), as this can deadlock    // with stack shrinking.    lock mutex}

Ring Buffer

The function is mainly composed of the following variables, a BUF store the actual data, two pointers for the send, receive the index position, with size, count in the array size range of sliding back and forth.

qcount   uint           // 当前队列中的元素个数dataqsiz uint           // 缓冲队列的固定大小buf      unsafe.Pointer // 缓冲数组sendx    uint   // 下一次发送的 indexrecvx    uint   // 下一次接收的 index

For example, suppose we initialize a channel with a buffer, the value of ch := make(chan int, 3) its initial state is:

qcount   = 0dataqsiz = 3buf      = [3]int{0, 0, 0} // 表示长度为3的数组sendx    = 0recvx    = 0

The first step is to send a value to the channel, ch <- 1 because the buffer is not yet full, so the post-operation status is as follows:

qcount   = 1dataqsiz = 3buf      = [3]int{1, 0, 0} // 表示长度为3的数组sendx    = 1recvx    = 0

Fast forward two, send two values (2, 3) to the channel continuously, with the following status:

qcount   = 3dataqsiz = 3buf      = [3]int{1, 2, 3} // 表示长度为3的数组sendx    = 0 // 下一个发送的 index 回到了0recvx    = 0

Receive a value from the channel <- ch with the following status:

qcount   = 2dataqsiz = 3buf      = [3]int{1, 2, 3} // 表示长度为3的数组sendx    = 0 // 下一个发送的 index 回到了0recvx    = 1 // 下一个接收的 index

Blocking

We look at how no data in the channel buffer is handled if the channel is receive. Logic in the chanrecv method, its approximate flow is as follows, preserving only the code of the blocking operation.

Func chanrecv (t *chantype, C *hchan, ep unsafe. Pointer, block bool) (selected, received bool) {//check if Channdel is nil//when not blocked, check buffer size, current size, check if Chennel is off, see        See if you can return directly//check whether the sender has a waiting goroutine, the lower section mentions//current buffer has data, then try to remove.    If non-blocking, direct return//No sender waits, no data in buffer, block wait. GP: = GETG () Mysg: = Acquiresudog () mysg.releasetime = 0 if T0! = 0 {mysg.releasetime =-1}//No    Stack splits between assigning elem and enqueuing mysg//on gp.waiting where copystack can find it. Mysg.elem = EP Mysg.waitlink = Nil gp.waiting = Mysg MYSG.G = GP Mysg.selectdone = Nil mysg.c = C Gp.par am = Nil C.recvq.enqueue (MYSG)//Key operation: Set Goroutine status to waiting, separate G and M goparkunlock (&c.lock, "Chan Receiv E ", TRACEEVGOBLOCKRECV, 3)//Someone woke us up//wake up, clean sudog if mysg! = gp.waiting {throw (" G waiting List is corrupted ")} gp.waiting = nil If mysg.releasetime > 0 {blockevent (mysg.releaseTime-t0, 2)} closed: = Gp.param = Nil Gp.param = Nil mysg.c = nil Releasesudog (MYSG) return True,!clo Sed

The operation here is to create a current Goroutine Sudog, and then put this sudog into the channel of the recipient wait queue, set the current state of G, and M separate, to this point the current G is blocked, the code will not be executed.
When awakened, performs a sudog cleanup operation. The pointer to accept the value in buffer is ep this variable, and is awakened as if there is no ep action assigned to the value. We'll talk about this in the next section.

Sudog

One last question remains, when a goroutine is blocked by the channel, and the other goroutine how to wake it up.

There are two waitq types of variables in the channel, look at the structure of the discovery, is sudog linked list, the key is Sudog. Sudog contains a reference to Goroutine, note elem the variable, and the comment says it might point to the stack.

type waitq struct {    first *sudog    last  *sudog}type sudog struct {    // The following fields are protected by the hchan.lock of the    // channel this sudog is blocking on. shrinkstack depends on    // this.    g          *g    selectdone *uint32 // CAS to 1 to win select race (may point to stack)    next       *sudog    prev       *sudog    elem       unsafe.Pointer // data element (may point to stack)    // The following fields are never accessed concurrently.    // waitlink is only accessed by g.    acquiretime int64    releasetime int64    ticket      uint32    waitlink    *sudog // g.waiting list    c           *hchan // channel}

Speaking of blocking part of the time, we saw Goroutine was dispatched before, there is an enqueue operation, then, the current G Sudog has been deposited recvq , we look at the delivery of the operation.

The operation here is that the value sent by sender is copied directly to the Sudog.elem. Then wake up the SUDOG.G so that the receiver goroutine on the opposite side is awakened. Please specify the following comments.

Func chansend (t *chantype, C *hchan, ep unsafe. Pointer, block bool, CALLERPC uintptr) bool {//Check work//If you can eject Recvq from Chennel Sudog, then direct send if SG: = C.RECVQ . Dequeue (); SG! = Nil {//Found a waiting receiver.        We pass the value we want to send//directly to the receiver, bypassing the channel buffer (if any). Send (c, SG, EP, func () {Unlock (&c.lock)}) return true}//buffer has free space, return; blocking Operation}func Send (c *hchan, SG *sudog, EP unsafe. Pointer, UNLOCKF func ()) {//Handle index//critical if sg.elem! = Nil {//Here is copy memory according to Elemtype.size Senddi Rect (C.elemtype, SG, EP) Sg.elem = nil}//some processing//reset Goroutine status, wake it Goready (GP, 4)}func Senddire CT (t *_type, SG *sudog, src unsafe.    Pointer) {//SRC is on our stack, and DST is a slots on another stack.    Once we read Sg.elem out of SG, it'll no longer//be updated if the destination ' s stack gets copied (shrunk). So make sure this no preemption points Can happen between read & use. DST: = Sg.elem typebitsbulkbarrier (t, UIntPtr (DST), UIntPtr (SRC), t.size) memmove (DST, SRC, t.size)}//memmove copie s n bytes from ' from ' to ' to './/In Memmove_*.s//go:noescapefunc memmove (to, from unsafe. Pointer, n uintptr)

Select

When looking at the chanrecv() method, a block parameter was found, which indicates whether the operation was blocked. In general, the channel is blocked (regardless of buffer), when is it not blocked?

The first thing to think about is select, when you write the default case, the other channel is non-blocking.

There is another may not commonly used, is the channel reflection value, can be non-blocking, this method is public, we first look at the simple.

func (v Value) TryRecv() (x Value, ok bool)func (v Value) TrySend(x Value) bool

Select is a little more complex, first found a note in the source code:

// compiler implements////    select {//    case c <- v://        ... foo//    default://        ... bar//    }//// as////    if selectnbsend(c, v) {//        ... foo//    } else {//        ... bar//    }//func selectnbsend(t *chantype, c *hchan, elem unsafe.Pointer) (selected bool) {    return chansend(t, c, elem, false, getcallerpc(unsafe.Pointer(&t)))}// compiler implements////    select {//    case v = <-c://        ... foo//    default://        ... bar//    }//// as////    if selectnbrecv(&v, c) {//        ... foo//    } else {//        ... bar//    }//func selectnbrecv(t *chantype, elem unsafe.Pointer, c *hchan) (selected bool) {    selected, _ = chanrecv(t, c, elem, false)    return}

If it is a case + default mode, then the compiler calls the above method to implement.

What if it's multiple case + default mode? How does the select run on runtime exactly? Write a simple select to compile a bit.

package mainfunc main() {    var ch chan int    select {    case <-ch:    case ch <- 1:    default:    }}

go tool compile -S -l -N test.go > test.sFind keywords in the results, for example:

0x008c 00140 (test.go:5)    CALL    runtime.newselect(SB)0x00ad 00173 (test.go:6)    CALL    runtime.selectrecv(SB)0x00ec 00236 (test.go:7)    CALL    runtime.selectsend(SB)0x0107 00263 (test.go:8)    CALL    runtime.selectdefault(SB)0x0122 00290 (test.go:5)    CALL    runtime.selectgo(SB)

Here selectgo is the actual way to run, look for, note the notes. First check whether the channel can operate, if not operation, go to the default logic.

Loop://Pass 1-look for something already waiting var DFL *scase var cas *scase for I: = 0; i < int (sel.ncase); i++ {cas = &scases[pollorder[i]] c = cas.c switch Cas.kind {//Accept data Case CASERECV            : SG = c.sendq.dequeue ()//If sender is waiting for if sg! = nil {goto recv }//Current buffer has data if C.qcount > 0 {goto BUFRECV}//Off Closed Channel if c.closed! = 0 {goto Rclose} case casesend:if Raceen abled {racereadpc (unsafe.            Pointer (c), cas.pc, CHANSENDPC)}//close if c.closed! = 0 {goto Sclose }//receiver is waiting for SG = c.recvq.dequeue () if SG! = nil {goto SE ND}//have space to accept if C.qcount < C.dataqsiz {gotoBufsend}//Go to default case CASEDEFAULT:DFL = cas}} if DFL! = Nil { Selunlock (scases, lockorder) cas = DFL goto retc}

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.