One of the design ideas of Go is based on Communicating sequential processes (CSP). This idea comes from Hoare’s 1978 paper. In this article, CSP is also the author’s own programming language, defining input and output statements for communication between processes. Processes are considered to require input to drive and produce output for consumption by other processes, which can be processes, threads, or even blocks of code. With these input-output commands, Hoare demonstrated that the problem of concurrent programming can be simplified if communication between processes is prioritized in a programming language. Go takes this idea further, and channel is the basis for information transfer between Goroutines.

The Channel usage

The statement

Like map and slice, channels can be initialized using the make keyword. A channel has a type attribute. A channel can only accept input and output of the corresponding type.

Ch := make(chan int) // make(chan int,2) // make(chan int,2) //Copy the code

A channel can be buffered or not. Unbuffered channels block when one goroutine writes to them until another goroutine writes to them. A channel with n buffers is allowed to input n values first, blocking only when the buffer is full and writing the n+1 value.

Read and write data

We use the <- operator to read and write a channel, and the direction of the arrow defines the direction of the data flow.

Ch := make(chan int) ch < -2 v := <-chCopy the code

Similarly, we can define read-only/write-only channels when declaring/initializing a channel, but read-only/write-only channels don’t make much sense and are usually declared this way only in function arguments.

Var readCh <-chan int // read only var writeCh chan< -int // write only var ch chan int // read and writeCopy the code

Or something like this

WriteCh := make (chan< -int,10) // writeCh := make (chan int,10) // writeCh := make (chan int,10) // writeCopy the code

Alternatively, you can use range to continuously read data from a channel

    c := make(chan int, 10)
    go fibonacci(10, c)
    for i := range c {
            fmt.Println(i)
    }
Copy the code

Shut down the channel

After a channel is declared, it can always be used to read and write data. In most cases, we do not need to actively clear a channel. When necessary, we can use close to actively close a channel, and only the sender can close the channel, but not the receiver. Sending data to a closed channel can cause panic.

func fibonacci(n int, c chan int) {
	x, y := 0, 1
	for i := 0; i < n; i++ {
		c <- x
		x, y = y, x+y
	}
	close(c)
}
Copy the code

The select keyword

The select statement in Go is similar to the switch statement in channel. Each case is a communication operation. If any case can be run, the corresponding statement will be executed. If multiple cases can be run, a case will be randomly selected for execution. If none is met, it blocks until one is met, which is why, in practice, it is best to have one for every select

package main

import (
	"fmt"
	"time"
)

func goRoutineA(ch chan int, i int) {
	for {
		time.Sleep(time.Second * 2)
		ch <- i
	}
}
func goRoutineB(ch chan string, in string) {
	for {
		time.Sleep(time.Second * 3)
		ch <- in
	}
}

func main() {
	intCh := make(chan int, 5)
	stringCh := make(chan string, 5)

	go goRoutineA(intCh, 5)
	go goRoutineB(stringCh, "ok")
	i := 1
	ok := true
	for ok {
		select {
		case msg := <-intCh:
			fmt.Println(i, " A input data ", msg)
		case msg := <-stringCh:
			fmt.Println(i, " B input data ", msg)
		default:
			fmt.Println(i, "no data ")
			time.Sleep(time.Second * 1)
		}
		i++
		if i > 60 {
			ok = false
		}
	}
}

Copy the code

Channel low-level implementation

Channel is to solve the problem of communication between different Goroutines, so for the channel is likely to face multiple writing goroutines and multiple reading goroutines, the channel’s bottom layer adopts the first-in, first-out queue to maintain this situation. The structure is defined in SRC /runtime/chan.go as follows

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 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
}
Copy the code

In addition, elemType is the type of elements that a channel can send and receive. Elemsize is the size of the element. The Closed attribute identifies whether a channel has been closed.

There are five main attributes related to processing buffer data: qcount is the number of data in the buffer, dataqsiz is the size of the buffer, buf is a pointer to the buffer, sendx is the current location of the send processing, and recvx is the location of the receive processing.

Sendq and Recvq are fifO queues mentioned earlier. Decibels store the Goroutine waiting to send and receive data. The specific structure is defined as WaitQ, where sudog is a Goroutine.

type waitq struct {
	first *sudog
	last  *sudog
}
Copy the code

The overall structure is shown in the diagram from the underlying implementation of a channel.

Other problems with Channel

How does a channel tell the corresponding Goroutine to operate

This involves the GMP model at the bottom of Go language. As mentioned above, the child node of each Goroutine queue is Sudog, and the bottom node of sudog is G. When the corresponding Gorouinte state changes due to the channel sending and receiving data, the underlying method is actually called to modify the state of G. Didn’t really get the Goruotine to work right away.

Is there any other way to communicate between Goroutines besides channel

Of course, the most common is shared memory, such as defining a global variable that is shared by multiple Goroutines. Another way is context, which literally translates to context, which is one of the more interesting mechanisms in GO.

Why is it recommended to use anonymous functions when reading or writing channels

It is not recommended to use anonymous functions, but to create another Goroutine to avoid deadlocks. For example, in the following example, create a channel, write data to it, because there is no data, will continue to block, the system determines that the deadlock.

func main() {
    ch := make(chan string)
    ch <- "send" 
}
Copy the code

If you switch to another goroutine, you don’t have this problem, because the main goroutine does whatever it wants to do, but the child goroutine blocks, and eventually the program exits normally.

func main() {
    ch := make(chan string)
    go func() {
        ch <- "send"
    }()
 
    time.Sleep(time.Second * 3)
}
Copy the code

The resources

A Tour Of Go

Concurrency in Go

Go language design and implementation

Illustrates the underlying implementation of a channel

Goroutine and Channel details

Cao Chunhui’s article