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