In the previous article, we introduced Goroutine, the basis of concurrent programming for Go, and several ways to use goroutine, but we didn’t explain how goroutines communicate with each other.
There is a classic saying in the Go language, do not communicate by shared memory, but communicate by shared memory. This principle makes channels an important component of the Go language.
Communication between Goroutines is mainly done through channels. In this article, we will take a look at channels and their basic use.
1. What is channel?
In Go language, there are two ways to realize the concurrent mode. One is to realize the synchronous access to each shared variable (memory) through traditional means such as lock and semaphore, so as to realize concurrency. Another way to implement concurrency is through a combination of Goroutine + channel, passing values.
Goroutine + Channel is a realization of CSP (Communicating Sequential Process) mode. In CSP mode, there are two core concepts, process and channel. Process corresponds to Groutine. All communication between processes is realized through channel.
A channel can be created independently and can be used to connect any two Goroutines. A channel also has its own data type, called the element type of the channel.
Creating a channel is simple, such as creating a channel that passes an int:
ch := make(chan int)
Copy the code
Chan represents the channel, int represents the type of element passed in the channel, and make creates a new channel. The result of make is a reference to a channel. It is important to understand that when you copy a channel or pass a channel as a function parameter, you pass a reference. By the way, channels are comparable, which means they can be compared by ==.
A channel has two operations, one is to send, one is to receive, both use <- to represent, the difference is that when sending, the channel first, after the channel received. To send data to a channel:
x := 5
ch <- x
Copy the code
It is also legal to receive a result from a channel and discard it if it is not assigned to a variable:
X := <-ch <-ch //Copy the code
A complete send and receive example is as follows:
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
x := 5
ch <- x
}()
y := <-ch
fmt.Println(y)
}
Copy the code
Deadlocks can occur when using channels, for reasons that will be discussed later. For a channel, there is another operation, which is to close the channel. For a closed channel, no data can be sent, otherwise panic will occur, but the receiving operation can be carried out, and the following program can run normally:
package main
import (
"fmt"
)
func main() {
ch := make(chan int)
go func() {
x := 5
ch <- x
close(ch)
}()
y := <-ch
fmt.Println(y)
}
Copy the code
2. No buffered channel
The make that creates the channel has a second parameter that specifies the channel capacity. If this parameter is not specified or 0 is specified, the channel is unbuffered:
Ch := make(chan int) ch := make(chan int, 0)Copy the code
A send operation on an unbuffered channel blocks until the receiving operation is complete, and then execution continues. In the last article, we used this method to solve the problem of waiting for the execution of the child Goroutine to complete. The code is as follows:
func goroutine2(isDone chan bool) { fmt.Println("child goroutine begin..." ) time.Sleep(2 * time.Second) fmt.Println("child goroutine end..." ) isDone <- true } func main() { isDone := make(chan bool) go goroutine2(isDone) <-isDone fmt.Println("main goroutine end.." )}Copy the code
Therefore, for unbuffered channels, do not use them in the same Goroutine, otherwise it will cause deadlock. Deadlocks are discussed in more detail below.
3. Buffer channel
When creating a buffer channel, you need to specify the capacity of the channel:
ch := make(chan int, 3)
Copy the code
The above code creates a channel of capacity 3 and can send values directly to the channel without blocking the first three operations:
ch <- 1
ch <- 2
ch <- 3
Copy the code
If in the process of sending, if the receiver does not receive, then the channel is full at this point and will block on the fourth value.
For buffered channels, the cap method can be used to get the capacity of the channel, and len method can be used to get the number of elements in the current channel:
Cap (ch) // Get the capacity len(ch) // Get the number of elementsCopy the code
For a buffered channel, using it in the same Goroutine also risks causing deadlocks, so it is best not to use channels in the same Goroutine.
4. Unidirectional channel
By default, the created channel can send and receive data, but in some cases we need the channel’s send or receive capability. At this point, a one-way channel is needed.
The representation of a one-way channel is simple. Placing <- before chan means receive only, and placing after chan means send only:
SendCh := male(chan< -int) // indicates the channel for sending only recCh := make(<-chan int) // indicates the channel for receiving onlyCopy the code
In practice, we do not need to create such a one-way channel, but in some cases, we can turn the channel into a one-way channel. For example, in the following code, in the sendData method, I only need to use the sending capability of the channel, so I can change the channel to send one-way channel, and other people will understand it better when reading the code:
func main() { ch := make(chan int, 10) sendData(ch) } func sendData(sendCh chan<- int) { for i := 0; i < 10; i++ { sendCh <- i } }Copy the code
A two-way channel can turn into a one-way channel, but not the other way around.
5. Summary
This article introduces channels, which are important to the Go language and are the basis for high concurrency, providing an efficient and secure way to communicate between Goroutines. However, deadlock issues need to be considered when using channels.
The text/Rayjun
This article was first published on wechat official account [Rayjun]