“This is my fourth day of participating in the First Challenge 2022. For more details: First Challenge 2022.”

What is chan? Why should I share memory by Communicating? How is the Chan base implemented? What is the difference between an unbuffered Chan and a buffered Chan? Does Chan lock data when sending and receiving data?

Golang Channel

Chan is a built-in data type in Golang. Unlike Mutex and others, chan is a first-class type and plays an important role in Go’s concurrency control. Chan’s idea came from Tony Hoare’s 1978 paper, Communicating Sequential Processes, which proposed a concurrent programming language to describe the interaction patterns in concurrent systems. In its later evolution, Only gradually formed the CSP concurrent mode. There are processes/channels in CSP mode. Each Process runs independently, and multiple processes communicate with each other through channels.

3, The core idea of Go concurrent control: Don’t communicate by sharing memory, share memory by communicating. That is, do not communicate through shared memory, but through communication to share memory, the former sentence corresponds to the traditional use of locks and other ways to control concurrency, the latter corresponds to CSP mode, goroutine/chan in GO corresponds to Process/Channel in CSP respectively.

Basic usage

In Go, Chan is a basic data type and can be defined like int64 for normal data type injection, except that each chan can only store certain types of data (including chan types), so when defining or creating a CHAN, you need to specify the data types it allows.

var cInt chan int
var cChan chan chan
Copy the code

If chan is nil, it will block if it operates on nil, so it must be initialized. Chan is initialized using make(), and can be initialized with a length (buffer length) as follows:

cIntWithBuff := make(chan int.10)
Copy the code

A chan with a specified length (buffered) is called buffered chan, or it can be initialized without a specified length and is called unBuffered chan, as follows:

cIntWithoutBuff := make(chan int)
Copy the code

In addition to defining buffers, we can also use <- to define chan readOnly or writeOnly. By default, we define chan in both directions:

readOnlyChan := make(< -chan int.0)
writeOnlyChan := make(chan<- int.0)
Copy the code

Chan reads and writes using the <- operator, as follows:

// write 1 to cInt chan
cInt <- 1

// Select an element from cInt chan
r, ok := <- cInt
Copy the code

When retrieving data from chan, two values are returned. The first is the value retrieved, and the second is a bool indicating whether the data was actually retrieved. If false is returned, chan has been turned off and there is not enough data in the buffer.

We can use close() to close a Chan, and sending data to a closed Chan will cause panic, but for a Buffered Channer we can still retrieve existing data (if any) from it, even if it has been shut down. For example:

func main(a) {
    cInt := make(chan int.1)
    go func(a) {
        cInt <- 1
    }()
    time.Sleep(1000)
    close(cInt)
    r, ok := <- cInt
    fmt.Println(r, ok)  // 1 true
    r, ok = <- cInt
    fmt.Println(r, ok)  // 0 false
}
Copy the code

In this case, the first return value will return zero when the buffer is empty, and the second return value is needed to determine whether there is any unconsumed data in chan.

On the block

For a Buffered Channer, if a Goroutine sends data to a buffered Channer that is already full, the Goroutine will block. Similarly, Fetching data from an empty Buffered Channer also blocks the Goroutine.

Len () is used to get the number of unfetched elements that exist in a channel

The capacity of a channel can be obtained through the cap() function.

An Unbuffered Channer does not block until both reads and writes are ready, as in:

package main

import (
 `fmt`
 `time`
)

type data struct {}

func send(ch chan<- data) {
 fmt.Println("send begin")
 ch <- data{}              / / blocking
 fmt.Println("send exit")  // Will not be executed
}

func main(a) {
 ch := make(chan data)
 go send(ch)
 time.Sleep(time.Second * 5)}// send begin
// 
// Process finished with exit code 0

Copy the code

In the code above, since ch is defined as an Unbuffered Channel, and we only send data to the chan without consuming it, the Goroutine opened in line 18 will be blocked. In development, This can lead to serious Goroutine leaks, and the solution here is to change the Unbuffered Channel to a Buffered Channel of length 1.

Other USES

Chan is also commonly used in conjunction with the for-range keyword, as in:

for v := range ch { 
    fmt.Println(v)
}

/ / clear channel
for range ch {
}
Copy the code

Another keyword used specifically with chan is select. This keyword has a similar syntax to switch, but its case must follow chan’s sending and receiving operations, for example:

func main(a) {
    ch := make(chan data, 1)
    for {
        select {
        case ch <- data{}:
            fmt.Println("send")
        case v := <-ch:
            fmt.Println(v)
        }
    }
}
Copy the code

The interesting thing about SELECT is that it does the chan operation without blocking, as in the above code if changed to the following:

func main(a) {
    ch := make(chan data, 1)
    for {
        select {
        // case ch <- data{}:
        // fmt.Println("send")
        case v := <-ch:
            fmt.Println(v)
        default:
            fmt.Println("default")}}}Copy the code

Since no one writes data like ch, if you block, goroutine is currently blocked until line 7, but in fact select executes default directly, so select is implemented as a multiplexing scheme. It will listen to whether multiple cases can be executed at the same time, and if there are multiple cases that can be executed at the same time, it will randomly select one to execute.

Because select is mainly used to listen on the state of the chan, if we need to listen on a large number of Chans, we can’t use hardcode to do this, so Go allows us to use reflect.Select to dynamically listen on multiple Chans:

The Select function takes a set of selectcases. Like a normal Select statement, it blocks until there are cases to execute. If there are multiple cases, it executes one at random.

func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool) {
    / /...
}
Copy the code
  • The first chosen is the index of the selected case in the list of selectCases
  • If the case performed is an accept operation, the second and third return values indicate the received value and whether it was received, just like normal value operations.

SelectCase is a structure containing the following three fields:

type SelectCase struct {
	Dir  SelectDir // direction of case
	Chan Value     // channel to use (for send or receive)
	Send Value     // value to send (for send)
}
Copy the code
  • Dir indicates the type of the case. The following three values are available:

    const (
    	_             SelectDir = iota
    	SelectSend              // case Chan <- Send
    	SelectRecv              // case <-Chan:
    	SelectDefault           // default
    )
    Copy the code
    • SelectSend indicates that the case performs the sending operationcase ch <- data{}:
    • SelectRecv indicates that the case performs an accept operation, similarlycase v := <-ch:
    • SelectDefault represents the default behavior in which the Chan and Send fields must have zero values.
  • Chan represents the Value of the channel to operate on. If Chan is nil, the case is ignored.

  • Send Is used to Send data to chan when Dir is SelectSend. In SelectRecv mode, Send must be zero.

A little chestnut:

func main(a) {
    ch := make(chan data, 1)
    chosen, recv, ok := reflect.Select([]reflect.SelectCase{
        {Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ch)},
        {Dir: reflect.SelectSend, Chan: reflect.ValueOf(ch), Send: reflect.ValueOf(data{})},
    })
    fmt.Println(chosen, recv, ok)
}
Copy the code

Implementation method

The data structure

The underlying implementation of chan can be seen in runtime/chan.go, from which we can see the underlying data structure of chan hchan:

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
}
Copy the code
  • Qcount: how much data is available in chan
  • Dataqsiz: Capacity of chan
  • Buf: buffer, is a circular queue, buF is the pointer to the circular queue
  • Elemsize: Specifies the size of an element in chan
  • Closed: indicates whether chan is closed
  • Elemtype: Specifies the type of the element in chan
  • Index of the last element received in sendx: chan, without inserting an element, this value is incremented by one, and added to dataqsiz will be incremented again from zero
  • Recvx: same as sendx, indicates the index of the next element in the ring queue that can be received.
  • Recvq: queue of blocked recipients
  • Sendq: blocked sender queue
  • Lock: a mutex lock that protects all fields
type waitq struct {
	first *sudog
	last  *sudog
}
Copy the code

Sendq and Recvq are both of type Waitq, which is a queue of type sudog. The first and last Pointers point to the beginning and end of the queue respectively. Sudog is a node in the queue, which represents a G. Golang uses the structure G to represent a goroutine, but here, since each G has a many-to-many relationship with chan, this means that each Goroutine could be in multiple different WaitQs, A Chan may also be waiting for more than one G, so sudog is used to represent the waiting G

create

The behavior of creating chan via make will eventually be converted to using runtime.makechan() or runtime.makechan64(), which handles buffers larger than 2322^{32}232, Makechan () is also called:

func makechan64(t *chantype, size int64) *hchan {
	if int64(int(size)) ! = size {panic(plainError("makechan: size out of range"))}return makechan(t, int(size))
}
Copy the code
const (
	maxAlign  = 8
    // chan the size of the object aligned with 8 bytes
	hchanSize = unsafe.Sizeof(hchan{}) + uintptr(-int(unsafe.Sizeof(hchan{}))&(maxAlign- 1))
	debugChan = false
)

func makechan(t *chantype, size int) *hchan {
	elem := t.elem

	// Security check to ensure that the data type stored in chan is less than 2^16
	if elem.size >= 1<<16 {
		throw("makechan: invalid channel element type")}// Check whether hchanSize is aligned with maxAlign
	ifhchanSize%maxAlign ! =0 || elem.align > maxAlign {
		throw("makechan: bad alignment")}// Use math.muluintptr to calculate em.size * size that is the desired buffer size
    // If overflow == true, multiplication overflows.
    // The request buffer is too large
	mem, overflow := math.MulUintptr(elem.size, uintptr(size))
	if overflow || mem > maxAlloc-hchanSize || size < 0 {
		panic(plainError("makechan: size out of range"))}var c *hchan
	switch {
	case mem == 0:
		// mem == 0, possibly because elem. Size == 0 or size == 0, only allocate hchan without allocating buffer space
		c = (*hchan)(mallocgc(hchanSize, nil.true))
		// Race detector uses this location for synchronization.
		c.buf = c.raceaddr()
	case elem.ptrdata == 0:
		// If the element is not a pointer, hchan and buf are allocated a contiguous memory space
		c = (*hchan)(mallocgc(hchanSize+mem, nil.true))
		c.buf = add(unsafe.Pointer(c), hchanSize)
	default:
		// If the element contains Pointers, buf will be allocated memory separately
		c = new(hchan)
		c.buf = mallocgc(mem, elem, true)
	}

	c.elemsize = uint16(elem.size)
	c.elemtype = elem
	c.dataqsiz = uint(size)

	return c
}
Copy the code

Makechan creates a chan in one of three ways:

  1. No buffer required: When an Unbuffered Channel is created or the element in a Channel is 0, only hchan is created
  2. When the element in a Channel contains no pointer, a contiguous block of memory is allocated for both hchan and BUF.
  3. When elements in a Channel contain Pointers, memory is allocated for buF separately to take the pressure off GC.

send

Sending data with ch < -i is eventually converted to runtime.chansend1(), which calls runtime.chansend():

func chansend1(c *hchan, elem unsafe.Pointer) {
	chansend(c, elem, true, getcallerpc())
}
Copy the code

This function takes four arguments:

func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {}
Copy the code
  • C: To chan
  • Ep: sent element
  • Block: Blocks if this value is true

Chansend is over 100 lines in total, but can be read in the following sections:

Part I: Judgments about Nil Chan:

if c == nil {
    if! block {return false
    }
    gopark(nil.nil, waitReasonChanSendNilChan, traceEvGoStop, 2)
    throw("unreachable")}Copy the code

If chan is empty, it will be judged by the block. If block == false is not required to block, false will be returned, but if chansend1 is called, block == false, so in general, an error will be raised if C is empty.

Block == false occurs in select:

func selectnbsend(c *hchan, elem unsafe.Pointer) (selected bool) {
    return chansend(c, elem, false, getcallerpc())
}
Copy the code

Part II:

if! block && c.closed ==0 && ((c.dataqsiz == 0 && c.recvq.first == nil) ||
		(c.dataqsiz > 0 && c.qcount == c.dataqsiz)) {
    return false
}
Copy the code

There are three criteria. In normal data transmission, since the block passed by Chansend1 is always true, in fact, the first criterion is not satisfied when data is sent through ch< -a. When block == false (which occurs when using select) we continue:

  1. C. closed == 0: chan is not closed

  2. C. dataqsiz == 0 && c.recvq.first == nil: BUF has 0 capacity and no receiver

  3. C. dataqsiz > 0 && c.qcount == c.dataqsiz: buf is full

Meet 1 && (2 | | 3), will be directly returns false, send failure

Part three: Lock

lock(&c.lock)
Copy the code

Part IV:

// If chan is disabled during sending, panic will occur
ifc.closed ! =0 {
    unlock(&c.lock)
    panic(plainError("send on closed channel"))}// dequeue() is used to return a receiver sudog from the head of the receiver queue and remove the sudog from the queue
If the receiver queue is empty, return nil.
// This is a clever point, when sending, if there is a Goroutine waiting to receive, directly to the data
// The waiting receiver, rather than putting it in a buffer for the receiver to fetch, can hint at some performance.
ifsg := c.recvq.dequeue(); sg ! =nil {
    // Bypass the write buffer and go directly to the receiver
    send(c, sg, ep, func(a) { unlock(&c.lock) }, 3)
    return true
}

// There is free space in the buffer, put the element into the buffer
if c.qcount < c.dataqsiz {
    // Calculate where the element should be stored in the buffer
    qp := chanbuf(c, c.sendx)
    // Copy the element to the buffer
    typedmemmove(c.elemtype, qp, ep)
    / / modify sendx
    c.sendx++
    // The buffer size is an integer of elemsize
    Sendx = 0 if sendx is equal to the queue size
    if c.sendx == c.dataqsiz {
        c.sendx = 0
    }
    // The number of elements in chan increases by one
    c.qcount++
    unlock(&c.lock)
    return true
}
Copy the code
func chanbuf(c *hchan, i uint) unsafe.Pointer {
	return add(c.buf, uintptr(i)*uintptr(c.elemsize))
}
Copy the code

The fourth part is the core of the whole send, he and the following points:

  1. Whether delivered directly to the receiver or placed in the buffer, locks are required

  2. If chan is shut down during sending, panic will be caused directly. Therefore, in general, shutting down chan is carried out by the sender, or the sender must be informed when the receiver shuts down chan.

  3. If there is a receiver in recVQ, the data can be directly sent to the receiver, avoiding the performance waste caused by writing to the buffer and reading again.

  4. The flow of writing data to the buffer is shown below:

Part 5: Blocking sender:

If the buffer is full or the Unbuffered Channel receiver is not ready, put the sender in sendq:

// Get the current g
gp := getg()
// Create a new sudog (i.e. a new node)
mysg := acquireSudog()

/ / fill sudog
mysg.releasetime = 0
ift0 ! =0 {
    mysg.releasetime = - 1
}
mysg.elem = ep
mysg.waitlink = nil
mysg.g = gp
mysg.isSelect = false
mysg.c = c
gp.waiting = mysg
gp.param = nil
// Insert a new node
c.sendq.enqueue(mysg)
// block the current goroutine until ChanparkCommit returns true
gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceEvGoBlockSend, 2)
KeepAlive(ep)
Copy the code

Part six: Being awakened

// someone woke us up.
ifmysg ! = gp.waiting { throw("G waiting list is corrupted")
}
gp.waiting = nil
gp.activeStackChans = false
if gp.param == nil {
    if c.closed == 0 {
        throw("chansend: spurious wakeup")}panic(plainError("send on closed channel"))
}
gp.param = nil
if mysg.releasetime > 0 {
    blockevent(mysg.releasetime-t0, 2)
}
mysg.c = nil
/ / destroy sudog
releaseSudog(mysg)
return true
Copy the code

recv

i <- ch
i, ok <- ch
Copy the code

Chanrecv1 () and Runtime.chanrecv2 (), which call Runtime.chanrecv () : chanrecv(); chanrecv();

//go:nosplit
func chanrecv1(c *hchan, elem unsafe.Pointer) {
	chanrecv(c, elem, true)}//go:nosplit
func chanrecv2(c *hchan, elem unsafe.Pointer) (received bool) {
	_, received = chanrecv(c, elem, true)
	return
}
Copy the code

Chanrecv takes the same structure as chansend and takes three arguments:

  1. C: Which chan to receive from
  2. Ep: Writes received data to ep, discards data if ep == nil.
  3. Block: identifies whether or not to block, where chanrecv1 and chanrecv2 both pass true
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool){... }Copy the code

The return value of chanrecv is two Booleans:

  1. Return if block == false and there are no elements available in chanfalse, flase
  2. Returns if chan is closedtrue, falseAnd ep is zero.
  3. Get the value to fill the EP and return when everything is oktrue, true

Again, the receiving process is divided into several parts:

Part 1: Handling nil chan:

if c == nil {
    if! block {return
    }
    gopark(nil.nil, waitReasonChanReceiveNilChan, traceEvGoStop, 2)
    throw("unreachable")}Copy the code

As with sending, receiving data from an uninitialized chan blocks the current Goroutine.

Part two: Quick failures in non-blocking states

/ / receive
if! block && (c.dataqsiz ==0 && c.sendq.first == nil ||
              c.dataqsiz > 0 && atomic.Loaduint(&c.qcount) == 0) &&
atomic.Load(&c.closed) == 0 {
    return
}

/ / send
if! block && c.closed ==0 && ((c.dataqsiz == 0 && c.recvq.first == nil) ||
		(c.dataqsiz > 0 && c.qcount == c.dataqsiz)) {
    return false
}
Copy the code

TODO: Same as sending, the first judgment is satisfied only when select is used, but the order is changed and atomic operations are used

Part three: Lock

lock(&c.lock)
Copy the code

Part IV:

// If chan is disabled and no data is available,
// This clears the data in the EP pointer returned by pathologists
ifc.closed ! =0 && c.qcount == 0 {
    unlock(&c.lock)
    ifep ! =nil {
        typedmemclr(c.elemtype, ep)
    }
    return true.false
}
Copy the code

This code corresponds to the following situation: there is no data in chan and chan has been turned off:

func main(a){
    ch := make(chan int.1)
    close(ch)
    r, ok := <- ch
    fmt.Println(r, ok)
}

// 0 false
Copy the code

Part five: Quick reception

ifsg := c.sendq.dequeue(); sg ! =nil {
    recv(c, sg, ep, func(a) { unlock(&c.lock) }, 3)
    return true.true
}
Copy the code

The same as the “direct send” mechanism, when receiving, if there is a blocked sender waiting to be sent in the SendQ queue, it will directly fetch the sender and receive data from him, avoiding writing to the buffer.

Part 6: Write buffer, block current Goroutine, wait to wake up, destroy sudog

These are as they were sent and will not be repeated.

close

The action of closing chan is eventually converted to a call to runte.closechan (), which can be understood in four parts:

  1. Exception handling
  2. Release all receivers
  3. Release all senders
  4. Rescheduling the blocked Goroutine

Part ONE: Exception handling

// Closing uninitialized chan causes panic
if c == nil {
    panic(plainError("close of nil channel"))
}

lock(&c.lock)

// Closing closed chan causes panic
ifc.closed ! =0 {
    unlock(&c.lock)
    panic(plainError("close of closed channel"))}// Change the flag bit
c.closed = 1
Copy the code

Part two: Release all receivers

var glist gList

// release all readers
for {
    // Get a sudoq from the recvq header
    sg := c.recvq.dequeue()
    if sg == nil {
        break
    }
    ifsg.elem ! =nil {
        typedmemclr(c.elemtype, sg.elem)
        sg.elem = nil
    }
    ifsg.releasetime ! =0 {
        sg.releasetime = cputicks()
    }
    gp := sg.g
    gp.param = nil
    // Put goroutine, the blocking receiver of recvq, into glist
    glist.push(gp)
}
Copy the code

Part 3: Release all Senders:

for {
    sg := c.sendq.dequeue()
    if sg == nil {
        break
    }
    sg.elem = nil
    ifsg.releasetime ! =0 {
        sg.releasetime = cputicks()
    }
    gp := sg.g
    gp.param = nil
    glist.push(gp)
}
Copy the code

Similar to the logic above, put all blocked senders into glist.

Part four: Dispatch all blocked Goroutines after unlocking

unlock(&c.lock)

// Ready all Gs now that we've dropped the channel lock.
for! glist.empty() { gp := glist.pop() gp.schedlink =0
    goready(gp, 3)}Copy the code

For rescheduling, see the following example:

func recv(ch <-chan data) {
    r, ok := <-ch
    fmt.Println(r, ok)
    fmt.Println("recv exit")}func main(a){
    ch := make(chan data)
    go recv(ch)
    time.Sleep(time.Second)
    close(ch)
    time.Sleep(time.Second)
    fmt.Println("main exit")}// {} false
// recv exit
// main exit
Copy the code

Since an Unbuffered Channel is created and only receivers are available, the newly opened goroutine in main is blocked on line 2 until ch is closed, returning {}, false

Chan should be used with caution

Chan is not a silver bullet for concurrent operations. Improper use of chan may lead to Goroutine leaks or program crashes. Generally speaking, the main cause of Goroutine leaks is blocking.

Send blocked condition

There are only two cases that will not block when sending:

  1. There are recipients in wait: The data is delivered directly to the recipients
  2. For a Buffereed Channel, data is put into the buffer when the buffer is full

In addition, the following three conditions can cause send blocking.

  1. Send data to uninitialized chan:

    if c == nil {
        gopark(nil.nil, waitReasonChanSendNilChan, traceEvGoStop, 2)
        throw("unreachable")}Copy the code
  2. Unbuffered Channel Receiver not ready: For an Unbuffered Channel, to avoid blocking, you must take the first route above, otherwise it will block.

  3. Buffered Channel The buffer is full.

Receive blocked cases

Read the source code, you can understand that receive does not block only two cases, and send similar:

  1. A blocked sender returns data directly from the sender.
  2. For a Buffered Channel, if there is data in the buffer, it is fetched directly from the buffer without blocking.

Otherwise, if chan is turned off and there is no data in the buffer, it will return directly.

And the receiver blocking situation is similar to amateur sending:

  1. Attempted to receive data from a Chan that was nil
  2. The Unbuffered Channel sender is not ready
  3. Buffered Channel The buffer is empty.

Cause panic

  1. Application failed because the object in chan is too large:

    mem, overflow := math.MulUintptr(elem.size, uintptr(size))
    if overflow || mem > maxAlloc-hchanSize || size < 0 {
        panic(plainError("makechan: size out of range"))}Copy the code

    Avoid: Consider using Pointers when objects are too large

  2. Trying to send data to a chan that has been shut down:

    ifc.closed ! =0 {
        unlock(&c.lock)
        panic(plainError("send on closed channel"))}Copy the code

    How to avoid it: The shutdown should be done by the sender as much as possible, because the receiver receiving data from a closed chan does not cause Panic, or the receiver must notify the sender when the chan is closed

  3. When trying to close a closed Chan:

    lock(&c.lock)
    ifc.closed ! =0 {
        unlock(&c.lock)
        panic(plainError("close of closed channel"))}Copy the code
  4. Attempted to close a nil chan

    if c == nil {
        panic(plainError("close of nil channel"))}Copy the code

    How to avoid it: Avoid creating nil chan

The lock

In the process of reading the source code, we find that chan locks all buf operations, but this lock is not the same as sync.Mutex implementation, the following lock:

This lock type in Chan is mutex defined in runtime2.go. Using Find Lead, it is used extensively in the underlying source code. According to the comments, it is as fast as a spin lock without contention (only a few machine instructions), but if a contention occurs, it will hibernate in the kernel. Its structure is simple:

type mutex struct {
	key uintptr
}
Copy the code

In different implementations, this key is different. In futex-based implementations, key is a uint32 value. In sema-based implementations, it is M* waitm:

The implementation can be seen in Mutex in the Go runtime

conclusion

For developers, chan is a synchronization scheme that is different from traditional shared memory. Producers send synchronized data to Chan, consumers get data from Chan, producers and consumers operate independently and communicate through Chan. However, in terms of the implementation of Buffered Channel, It should still be done by locking and sharing memory (shared BUF).

The important points in the source code are as follows:

  1. Chan underlying data structure is Hchan.
  2. When chan is created, buf’s address space and Hchan are contiguous if the stored type does not contain Pointers, because the size of each element is fixed without Pointers.
  3. The data types stored in chan are limited in size, and Pointers are recommended if the object is too large or uncertain
  4. Locks are still used when reading or writing data in chan.
  5. There is a fast channel for sending or receiving data: if there is a wait, the data is traded directly with the wait.
  6. The BUF created in a Buffered Channel is a circular queue where data that is too late to receive is placed.
  7. When a send or receive is blocked, the blocked goroutine is added to sendq or RECvq, which is a bidirectional linked list of SUdoQ.
  8. Shutting down chan does not destroy buF, only dispatches all blocking G’s, so data can be read from the closed chan.

reference

  1. [Go Concurrent Programming Practical Class]
  2. Design and Implementation of Go language in DraP
  3. Golang’s Chan
  4. Journey-C’s Journey-C’s Journey-C