This is the 8th day of my participation in the August More Text Challenge

Let’s start with an interview question:

What about reading and writing to chan that has been shut down? Why is that?Copy the code

In my previous article on Go coroutines, I learned that the Go keyword can be used to start a Goroutine for task processing, but channels are needed if multiple tasks need to communicate.

I. Definition of Channel

We can use the Go built-in function make to specify the element type of the channel type. We declare a channel of type chan int:

ch := make(chan int)

Copy the code

2. Operation of Channel

Send (write) : The send operation consists of two steps: copy the element value and place the copy inside the channel. That is, it is not the element value to the right of the operator that enters the channel, but its copy.

ch := make(chan int)

// write to channel
ch <- x

Copy the code

Receive (read) : The receive operation includes three steps: “Copy the element value in the channel”, “place the copy to the receiver” and “delete the original value”.

ch := make(chan int)

// read from channel
x <- ch

// another way to read
x = <- ch

Copy the code

Closing: Closing a channel produces a broadcast mechanism, and all Goroutines that read messages from the channel receive messages.

ch := make(chan int)

close(ch)

Copy the code

Reading a message from a closed channel never blocks, and returns an OK-idiom of false, which can be used to determine whether a channel is closed:

v, ok := <-ch

Copy the code

If OK is false, the received v is the generated zero value, and the channel is closed or empty.

3. Characteristics of Channel sending and receiving operations

  1. A channel is equivalent to a first-in, first-out (FIFO) queue: that is, the values of the elements in the channel are arranged in the exact order in which they were sent, and the values of the elements sent in the first channel must be received first.

  2. For the same channel, send and receive operations are mutually exclusive: multiple elements are sent to the same channel at the same time, and no other send operations can be performed on that channel until the value of the element is copied into the channel. The same is true of reception.

  3. Send and receive operation, the handling of the element value is indivisible: we know that send a value to the channel in front, is to copy the value first, then move the copy into the channel, “inseparable” refers to the send operation or haven’t copy the element value, or is copy, copy only the part of the would never happen. The same is true for receiving. After a copy of the element value is prepared, the original value in the channel is always deleted, and there is never a residue in the channel.

  4. The send and receive operations are blocked until they are complete: the send operation consists of two steps: copy the element value and place the copy inside the channel. Until these two steps are complete, the code that initiated the send operation blocks, and the code after it has no chance to execute until the block is cleared.

4. Type of Channel

Channels are classified into channels without caching and channels with caching.

When you declare a channel type variable with make, in addition to specifying the element type of the channel, you can also specify the capacity of the channel, that is, how many element values the channel can cache. When the capacity is 0, the channel is unbuffered, and when the capacity is greater than 0, the channel is buffered.

Ch := make(chan int) // unbuffered channel ch := make(chan int, 3) // cached channelCopy the code

Non-buffered channels and buffered channels have different data transfer modes:

  • Unbuffered channels: Both send and receive operations are blocked at the beginning of execution and do not continue until the paired operation is also executed. That is, data is transmitted only when the sender and receiver are connected. Data is copied directly from the sender to the receiver. Unbuffered channels pass data synchronously. The following code execution will report an error
package main

import (
    "fmt"
)

func main() {
    ch := make(chan int)
    ch <- 1

    v := <-ch
    fmt.Println(v)
}

Copy the code

Error as follows:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
	/Users/didi/Documents/learn/src/chan.go:9 +0x59
exit status 2

Copy the code

This is because you are creating an unbuffered channel.

  • Buffered channel: If the channel is full, all sending operations to it are blocked until an element value in the channel is received. Conversely, if the channel is empty, all receive operations to it are blocked until a new element value appears in the channel. Element values are copied from the sender to the buffer channel, and then from the buffer channel to the receiver. The buffer channel passes data asynchronously.

Five, Channel source code learning

The main implementation of chansend is in SRC/Runtime /chan.go, go version go1.14.6 Darwin /amd64.

func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool { if c == nil { if ! block { return false } gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2) throw("unreachable") } if debugChan { print("chansend: chan=", c, "\n") } if raceenabled { racereadpc(c.raceaddr(), callerpc, funcPC(chansend)) } // Fast path: check for failed non-blocking operation without acquiring the lock. // // After observing that the channel is not closed, we observe that the channel is // not ready for sending. Each of these observations is a single word-sized read // (first c.closed and second c.recvq.first or c.qcount depending on kind of channel). // Because a closed channel cannot transition from 'ready for sending' to // 'not ready for sending', even if the channel is closed between the two observations, // they imply a moment between the two when the channel was both not yet closed // and not ready for sending. We behave as if we observed the channel at that moment, // and report that the send cannot proceed. // // It is okay if the reads are reordered here: if we observe that the channel is not // ready for sending and then observe that it is not closed, that implies that the // channel wasn't closed during the first observation. if ! block && c.closed == 0 && ((c.dataqsiz == 0 && c.recvq.first == nil) || (c.dataqsiz > 0 && c.qcount == c.dataqsiz)) { return false } var t0 int64 if blockprofilerate > 0 { t0 = cputicks() } lock(&c.lock) if c.closed ! = 0 { unlock(&c.lock) panic(plainError("send on closed channel")) } 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) }, 3) return true } if c.qcount < c.dataqsiz { // Space is available in the channel buffer. Enqueue the element to send. qp  := chanbuf(c, c.sendx) if raceenabled { raceacquire(qp) racerelease(qp) } typedmemmove(c.elemtype, qp, ep) c.sendx++ if c.sendx == c.dataqsiz { c.sendx = 0 } c.qcount++ unlock(&c.lock) return true } if ! block { unlock(&c.lock) return false } // Block on the channel. Some receiver will complete our operation for us. 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 mysg.g = gp mysg.isSelect = false mysg.c = c gp.waiting = mysg  gp.param = nil c.sendq.enqueue(mysg) gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceEvGoBlockSend, 2) // Ensure the value being sent is kept alive until the // receiver copies it out. The sudog has a pointer to the // stack object, but sudogs aren't considered as roots of the // stack tracer. KeepAlive(ep) // someone woke us up. if mysg ! = 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 releaseSudog(mysg) return true }Copy the code

As you can see from the code:

  • There is a Goroutine blocking on the Channel recV queue, the cache queue is empty, and the message is sent directly to the Reciever Goroutine, making only one copy.

  • When the channel cache queue has free space, it puts the data into the queue and waits for receiving. After receiving, a total of two copies are generated.

  • When the channel cache queue is full, the current Goroutine is added to the SEND queue and blocked.


So, here’s the answer to the opening interview question: Read: Read the closed Chan and read the contents until the contents are read, but the contents will vary depending on whether there were elements in the channel before it was closed. If there are elements in the buffer that have not been read before chan is turned off, the value in chan is read correctly and the second bool returned is true. If the buffer has been read before chan is closed but chan has no value, return the channel element with a zero value and the second bool false. Write: Write a closed chan panic.