1 Why is there a channel

Goroutine is a new feature of Go, and it’s this killer that makes Go so popular that many passers-by stop and admire it, and believers cheer and talk about it.

The use of coroutines is also very simple. Using the keyword “Go” in Go followed by the function to be executed means that a new coroutine is started to execute the function code.

func main(a) {
    go test()
    fmt.Println("it is the main goroutine")
    time.Sleep(time.Second * 1)}func test(a) {
    fmt.Println("it is a new goroutine")}Copy the code

To put it simply, the coroutine in Go is a concurrency mechanism that is lighter and supports higher concurrency.

Take a closer look at the operation that sleeps for a second in the main function above. If the line is removed, the “it is a new goroutine” will not be printed. This is because the main coroutine ends before the newly started coroutine can run.

So the question is, how do we make it so that coroutines can tell each other if they’re done?

Obviously, we can sleep the main coroutine for a second, wait for the child coroutine, and make sure the child coroutine executes. But as a new language, it should not be used in such a low way. Even the older Generation of Java has the asynchronous mechanism Future, and can block the execution of waiting tasks through the get method, to ensure that the execution status of asynchronous processes can be known in the first time.

So Go has to have something special, another feature that gets people’s attention and drives believers crazy — a channel.

2 How to use the channel

A channel can simply be thought of as a communication bridge between coroutine goroutines, which can be used to communicate between coroutines with ease and be thread-safe.

2.1 Channel Classification

Channels fall into two categories

Unbuffered channel

ch := make(chan string)
Copy the code

Buffered channel

ch := make(chan string.2)
Copy the code

2.2 Differences between the two types of channels

1. The buffer has the capacity to hold two variables of type STIRng

The unbuffered channel itself does not store information, it is only responsible for the transfer of hands, someone to it, it must be passed to others, if only in or only out operations, will cause blocking. Buffered variables can store a specified number of variables, but exceeding this number will block.

2.3 Examples for using the two Channels

Unbuffered channel

func main(a) {
    ch := make(chan string)
    go func(a) {
        ch <- "send"
    }()
    
    fmt.Println(<-ch)
}
Copy the code

Start a new coroutine in the main coroutine, which is an anonymous function, and send “send” to the channel in the sub-coroutine. By printing the result, we know that the value passed to CH was received in the main thread using <-ch.

<-ch is a shorthand. You can also use STR := <-ch to receive channel values.

Above is in the subcoroutine to the channel, and the value of the main coroutine, or vice versa, can also print the channel value normally.

func main(a) {
	ch := make(chan string)
	go func(a) {
		fmt.Println(<-ch)
	}()

	ch <- "send"
}
Copy the code

Buffered channel

func main(a) {
    ch := make(chan string.2)
    ch <- "first"
    ch <- "second"
    
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}
Copy the code

The execution result is

first
second
Copy the code

The channel itself is structured as a first-in, first-out queue, so the order of output here is shown as the result.

There is no need to restart a Goroutine from the point of view of the code, and no deadlocks occur (for reasons explained later).

3 channel closing and traversal

3.1 shut down

The channel can be closed. The syntax is the same for unbuffered and buffered channel closure.

close(channelName)
Copy the code

Note that if the channel is closed, no value can be sent to the channel, otherwise an error will be reported.

func main(a) {
	ch := make(chan string.2)
	ch <- "first"
	ch <- "second"

	close(ch)

	ch <- "third"
}
Copy the code

Error message

panic: send on closed channel
Copy the code

3.2 traversal

Buffered channels are capacious, so they are traversable, and support the familiar range traversal.

func main(a) {
	chs := make(chan string.2)
	chs <- "first"
	chs <- "second"

	for ch := range chs {
		fmt.Println(ch)
	}
}
Copy the code

The output is

first
second
fatal error: all goroutines are asleep - deadlock!
Copy the code

That’s right, if you run out of channel information and then try to fetch it, it will also deadlock.

4 Channel deadlock

With this introduction, we have a general idea of what a channel is and how to use it.

Here are the scenarios for channel deadlocks and why they occur.

4.1 Deadlock scene 1

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

Deadlock occurs in both cases, either when values are passed to an unbuffered channel or when values are sent.

Cause analysis,

The above scenario is in the case of only one goroutine, the main goroutine, using an unbuffered channel.

As mentioned earlier, unbuffered channels do not store values and block both passing and passing values. Here, with only one main coroutine, the first piece of code blocks passing values, and the second piece of code blocks taking values. Because the main coroutine is stuck and the system is waiting, the system determines that the deadlock is present and finally reports an error and terminates the program.

extension

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

Deadlock does not occur in this case.

Some say that the main coroutine starts too fast, and the sub-coroutine leaves before it can see it, so the deadlock ends before the negotiation can complain.

That’s not the case. Here’s a counter example

func main(a) {
	ch := make(chan string)
	go func(a) {
		ch <- "send"
	}()

	time.Sleep(time.Second * 3)}Copy the code

This time the main coroutine waited for you for three seconds, three seconds you should be done, right? !

However, from the execution results, there is no child coroutine that causes a deadlock error because it blocks all the time.

This is because although subcoroutines are always blocking value statements, this is only a matter of subcoroutines. The main coroutine out there will do whatever it takes, and you’ll be on your way in three seconds. The main coroutine is over, so the subcoroutine is over.

4.2 Deadlock Scene 2

Following the extended scene of deadlock scene 1 above, we mentioned that the extended scene did not have a deadlock because the main coroutine left, so the child coroutines had to go home. That is, there is no coupling between the two.

Student: Is it deadlocked if they establish a connection over a channel?

func main(a) {
    ch1 := make(chan string)
    ch2 := make(chan string)
    go func(a) {
        ch2 <- "ch2 value"
        ch1 <- "ch1 value"
    }()
    
    <- ch1
}
Copy the code

The execution result is

fatal error: all goroutines are asleep - deadlock!
Copy the code

That’s right, that’s where the deadlock happens.

Cause analysis,

The above code does not guarantee that the <-ch1 of the main thread or the code of the subcoroutines will be executed first.

If the main coroutine executes to <-ch1 first, it will obviously block waiting for some other coroutine to pass to CH1. Ch2 <- “ch2 value”, ch2 <- “ch2 value”, ch2 <- “ch2 value”

The ch1 subcoroutine is waiting for the receiver of CH2. The ch1<- “CH1 value” statement fails to get the right to execute, so everyone is waiting for each other. The system can no longer look at it and decides to deadlock, and the program ends.

Conversely, the order of execution is the same.

extension

Some people say, well, can I avoid deadlocks by doing this

func main(a) {
	ch1 := make(chan string)
	ch2 := make(chan string)
	go func(a) {
		ch2 <- "ch2 value"
		ch1 <- "ch1 value"
	}()

	<- ch1
	<- ch2
}
Copy the code

No, the execution result is still deadlocked. Because this order still does not change the situation in which the master and subcoroutines wait for each other, which is the trigger condition for a deadlock.

Change to the following so that you can end normally

func main(a) {
	ch1 := make(chan string)
	ch2 := make(chan string)
	go func(a) {
		ch2 <- "ch2 value"
		ch1 <- "ch1 value"
	}()

	<- ch2
	<- ch1
}
Copy the code

Therefore, the following example is used to verify the deadlock scene 1 above because the main coroutine is not affected by the deadlock, so it will not report deadlock errors

func main(a) {
	ch1 := make(chan string)
	ch2 := make(chan string)
	go func(a) {
		ch2 <- "ch2 value"
		ch1 <- "ch1 value"} ()go func(a) {
		<- ch1
		<- ch2
	}()

	time.Sleep(time.Second * 2)}Copy the code

We just saw if

<- ch1
<- ch2
Copy the code

If you put it on the main coroutine, you’re going to have a deadlock because you’re waiting for each other. However, in this example, the same code is placed in a newly started coroutine. Although the two child coroutines block deadlock, it does not affect the main coroutine, so the program execution will not report deadlock errors.

4.3 Deadlock scene 3

func main(a) {
	chs := make(chan string.2)
	chs <- "first"
	chs <- "second"

	for ch := range chs {
		fmt.Println(ch)
	}
}
Copy the code

The output is

first
second
fatal error: all goroutines are asleep - deadlock!
Copy the code

Cause analysis,

Why would a deadlock occur after printing all cached values for the CHS channel?

In fact, it is very simple. Although CHS is a channel with buffering, it has only two capacity. When the two outputs are finished, the channel can be simply equated to the channel without buffering.

Obviously for unbuffered channels simply reading elements would block, and in the main coroutine, so it’s equivalent to deadlock field 1, so it’s deadlocked.

5 concludes

1. Channel is the bridge of communication between coroutines

2. Channel is divided into unbuffered channel and buffered channel

3. When using the channel, we should pay attention to whether deadlock is formed and the causes of various deadlock