The implementation of Go channel

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

Channel, as an important way of communication and synchronization between Goroutine, is an important member of the Go Runtime layer to implement CSP concurrency model. When you do not understand the underlying implementation, you often have doubts about the performance of the Channe-related syntax in use, especially the behavior of Select Case. So take a look at the channel implementation before you understand the application of the channel.

Channel Memory layout

The channel is the built-in type of go, which can be stored in a variable, and can be used as a function parameter or return value, which is Hchanin the data structure of the runtime layer. Hchan maintained a list of two linked lists, and Recvq was blocked by reading this Chan G,SENDQ the G that was blocked by writing this Chan. The data structure for each element in the WAITQ queue is Sudog, where elem is used to hold the information.

type hchan struct {    qcount   uint           // total data in the queue    dataqsiz uint           // size of the circular queue    buf      unsafe.Pointer // points to an array of dataqsiz elements    elemsize uint16    closed   uint32    elemtype *_type // element type    sendx    uint   // send index    recvx    uint   // receive index    recvq    waitq  // list of recv waiters    sendq    waitq  // list of send waiters    lock     mutex}type sudog struct {    g           *g    selectdone  *uint32    next        *sudog    prev        *sudog    elem        unsafe.Pointer // data element    releasetime int64    nrelease    int32  // -1 for acquire    waitlink    *sudog // g.waiting list}

The Hchan is just the head of the channel, and a contiguous array of memory behind the head acts as a buffer for the channel, the ring queue used to hold the channel data . Qcount DataSize describes the current usage and capacity of buffers respectively. If the channel is unbuffered, the size is 0 and there is no such ring queue.

Creating Chan requires knowing the data type and buffer size. The corresponding structure diagram newarray will generate this ring queue. The reason to separate the pointer type buffers is primarily to differentiate the GC operation, which needs to be set to Flagnoscan. And the pointer size is fixed, you can allocate memory with Hchan head, no need to first new(hchan) newarry .

Chan, which declares but does not initialize, is nil Chan. Read/write Nil Chan will block and close Nil Chan will panic.

func makechan(t *chantype, size int64) *hchan {    elem := t.elem    var c *hchan    if elem.kind&kindNoPointers != 0 || size == 0 {    c = (*hchan)(mallocgc(hchanSize+uintptr(size)*uintptr(elem.size),     nil, flagNoScan))        if size > 0 && elem.size != 0 {            c.buf = add(unsafe.Pointer(c), hchanSize)        } else {            c.buf = unsafe.Pointer(c)        }    } else {        c = new(hchan)        c.buf = newarray(elem, uintptr(size))    }    c.elemsize = uint16(elem.size)    c.elemtype = elem    c.dataqsiz = uint(size)    return c}

Channel operations

Read and write from the implementation of Chan all to lock, which is the same as read and write shared memory has the lock overhead.

The direction in which the data is passed from Chansend begins with the data field of the Goroutine in the RECVQ, which, if blocking occurs, may be written to the Goroutine data domain in SENDQ to wait for transit.

Sudog objects can be reused after returning from Gopark.

Synchronous Read and Write

Write channel c<-x calls runtime.chansend . Read Channel <-c calls runtime.chanrecv . The process of summarizing synchronous reading and writing is:

    • When you write Chan, you have to check the RECVQ for the goroutine of Chan, if there is a sudog from RECVQ. syncsendthe Data EP that will be written to Chan is copied to the Elem domain of the sudog that was just out of the team. By goready Waking the receiver G, the status is set to _Grunnable , and then put in the P local to run queue. After this the read to the data of G can again be dispatched by P.
    • When writing Chan, if there is no g waiting to be read, current g is blocked by waiting for writing. At this point acquireSudog , the data to be written on the package is created or fetched into the SENDQ queue. At the same time the current G gopark sleep wait is awakened.
    • When you read Chan, you first wake up the goroutine waiting to be written in the SENDQ and get the data from it, and if no one writes, hang yourself in the recvq waiting to wake up.
 func chansend (t *chantype, C *hchan, ep unsafe.)        Pointer, block bool, CALLERPC uintptr) bool {... lock (&c.lock) If C.dataqsiz = = 0 {//synchronous channel SG: = C.recvq.dequeue () if SG! = nil {//found a waiting receiver unlock (&c.lock) recv G: = Sg.g syncsend (c, SG, EP) Goready (RECVG, 3) return true}//No Receive        R Available:block on the This channel. MYSG: = Acquiresudog () Mysg.elem = EP C.sendq.enqueue (MYSG) goparkunlock (&c.lock, "Chan send",        Traceevgoblocksend, 3)//Someone woke us up. Releasesudog (MYSG) return True}}  
func chanrecv(t *chantype, c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {    if c.dataqsiz == 0 { // synchronous channel        sg := c.sendq.dequeue()        if sg != nil {            unlock(&c.lock)            typedmemmove(c.elemtype, ep, sg.elem)            gp.param = unsafe.Pointer(sg)            goready(gp, 3)            return true, true        }        // no sender available: block on this channel.        mysg := acquireSudog()        mysg.elem = ep        c.recvq.enqueue(mysg)        goparkunlock(&c.lock, "chan receive", traceEvGoBlockRecv, 3)        // someone woke us up        releaseSudog(mysg)        return recvclosed(c, ep)    }}

asynchronous read and write

The difference between asynchronous and synchronous is that the read-write priority is to check that the buffer has no data read or no space to write. And when you actually read and write to Chan, a buffer change occurs, and the goroutine that might have previously been blocked will have the opportunity to write and read, so try to wake them up. Summary process:

    When
    • writes Chan, the buffer is full, and the current G and data are encapsulated into the SENDQ queue waiting to be written, while the Gopark current goroutine is suspended. If the buffer is not full, the data is written directly to the buffer and the index and qcount of the latest buffer data are updated. At the same time, try to wake Goready from RECVQ with a wait-read goroutine that was previously blocked by a buffer that is not readable. The
    • reads Chan first to see if the buffer has no data, if any, to read directly, and to attempt to wake up a wait-write that was blocked by a full buffer before goroutine, giving it the opportunity to write the data. If there is no data to read the queue recvq.
 func chansend (t *chantype, C *hchan, ep unsafe.) Pointer, block bool, CALLERPC uintptr) bool {//asynchronous channel Var t1 int64 for futile: = byte (0); C.qcoun T >= C.dataqsiz; Futile = tracefutilewakeup {mysg: = Acquiresudog () c.sendq.enqueue (MYSG) goparkunlock (&c.lock, " Chan send ", Traceevgoblocksend|futile, 3)//Someone woke US up-try again Releasesudog (MYSG)}//WR ITE our data into the channel buffer Typedmemmove (C.elemtype, Chanbuf (c, C.sendx), EP) c.sendx++ if c.sendx = = c. dataqsiz {c.sendx = 0} c.qcount++//Wake up a waiting receiver sg: = C.recvq.dequeue () if SG! = N Il {goready (SG.G, 3)} return true}  
func chanrecv(t *chantype, c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {    // asynchronous channel    for futile := byte(0); c.qcount <= 0; futile = traceFutileWakeup {        mysg := acquireSudog()        c.recvq.enqueue(mysg)        goparkunlock(&c.lock, "chan receive", traceEvGoBlockRecv|futile, 3)        // someone woke us up - try again        releaseSudog(mysg)    }    typedmemmove(c.elemtype, ep, chanbuf(c, c.recvx))    memclr(chanbuf(c, c.recvx), uintptr(c.elemsize))    c.recvx++    if c.recvx == c.dataqsiz {        c.recvx = 0    }    c.qcount--    // ping a sender now that there is space    sg := c.sendq.dequeue()    if sg != nil {        goready(sg.g, 3)    }    return true, true}

Shut down

Wake up the goroutine waiting to be read in RECVQ by Goready, then wake up Goroutine waiting to be written in all SENDQ. so Close Chan is the equivalent of releasing all gouroutine that are blocked by it.

func closechan(c *hchan) {    c.closed = 1    // release all readers    for {        sg := c.recvq.dequeue()        if sg == nil {            break        }...        goready(gp, 3)    }    // release all writers    for {        sg := c.sendq.dequeue()        if sg == nil {            break        }...        goready(gp, 3)    }}

Writing closed Chan or closing closed Chan will lead to panic. Read closed Chan will never block, return a 0 value of a channel data type, and return the parameter EP to the function.

Therefore, it is often necessary to read in close Chan to determine if Chan is closed.

if v, open := <- c; !open {   // chan is closed}

Happens before

It's interesting to talk about the happens-before problem in go memory model. Some of these sync rules that are related to Chan can explain some of the questions that have always been recorded as follows:

    • Write operation with buffered Chan Happens-before corresponding Chan's read operation
    • Close Chan Happens-before read the last return value from the Chan 0
    • Non-buffered Chan's read operation Happens-before corresponding Chan's write operation
var c = make(chan int, 10)var a stringfunc f() {  a = "hello, world"  //(1)  c <- 0  // (2)}func main() {  go f()  <- c  //(3)  print(a)  //(4)}

(1) Happens-before (2) (3) Happens-before (4), then according to the rules (2) happens (3). So (1) Happens-before (4), this code is no problem, will definitely output Hello world.

var c = make(chan int)var a stringfunc f() {  a = "hello, world"  //(1)  <-c  // (2)}func main() {  go f()  c <- 0  //(3)  print(a)  //(4)}

Also according to the rule three (2) Happens-before (3) can finally be guaranteed (1) happens-before (4). If C is changed to a buffered Chan, the result will no longer have any synchronization guarantees (2) Happens-before (3).

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.