In the previous article, I introduced the important keywords for Golang to achieve concurrencygo
, through this we can easily and quickly start the Goroutinue coroutine. There must be communication requirements between coroutines, and Golang's core design idea is:Memory should be shared not by sharing memory. There is a slight difference from other programming languages that pass data through shared memory, and Channel is the one that implements this solution.
Channel is a queue type that provides data types for concurrent function communication that can receive and send specific types of values, and meets the FIFO (first in, first out) principle. FIFO is reflected in both data types and operations:
- Elements of Channel type are first-in, first-out, and elements sent to Channel first will be received.
- Goroutinue that sends data to the channel first will be executed first
- Goroutinue that receives data from the channel first will be executed first
Channel usage
grammar
channel is a data type in Golang, and the related syntax is also very simple.
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType
channel is a channel type keyword
<- operator is used for sending and receiving data in channel, and is used to indicate the direction of channel data flow when declared.
- chan defaults to two-way delivery, that is, channel can receive and send data
- chan<- Only channel that can send data
- <-chan only channel that can accept data
ElementType represents element type, such as int, string...
initialization
The channel data type is a reference type, similar to map and slice, so the initialization of the channel requires the use of the built-in function make():
make(ChannelType, Capacity) ch := make(chan int) var ch = make(chan int) ch := make(chan int, 10) ch := make(<-chan int) ch := make(chan<- int, 10)
- ChannelType is the type introduced earlier
- Capacity stands for buffer capacity. When omitted, it is the default 0, indicating an unbuffered Channel.
If the channel is not initialized without using the make() function, the sending and receiving communication operations cannot be performed, and it will cause blockage, which will cause Goroutinue leakage. Example:
func main() { defer func() { ("goroutines: ", ()) }() var ch chan int go func() { <-ch }() () }
The code execution result is:
goroutines: 2
As you can see, until the program exits, the number of Goroutinues is still 2. The reason is that the channel is not initialized correctly using make(), and the channel variable is actually nil, which in turn causes memory leakage.
Receiving and sending data
The reception and transmission of data in the channel are operated through the operator <-:
// Receive datach <- Expression ch <- 111 ch <- struct{}{} // Send data<- ch v := <- ch f(<-ch)
In addition to the operator <-, we can also usefor range
Continuously receive data from the channel:
for e := range ch { // e reads the element values in ch one by one}
There is no big difference between continuous reception and <-:
- If ch is nil channel, it will block
- If ch does not send the element, it will block.
for will continue to read until the channel is closed. After closing, for will read all remaining elements and end. So what happens when sending and receiving data on a closed channel?
Channel's closure
After using the channel, use the built-in function close() to close the channel. Turning off the channel means recording that the channel cannot be sent any elements anymore, rather than destroying the channel. This means that the closed Channel can continue to receive the value.
- If sending data to a closed channel, panic will be triggered
- Close nil channel will trigger panic
- Close the closed channel will cause panic
- If you read the channel value that has been closed, you can receive all the values sent before closing; after receiving the value before closing, the zero value of type and a false will be returned, and will not block and panic
You can write a simple code to test the above situations.
Channel classification
As mentioned earlier, when making a channel, the second parameter represents the buffer size. If there is no second parameter, it is the default unbuffered channel. Specific usage:
- Buffer Channel, make(chan T, cap), cap is a value greater than 0.
- Unbuffered Channel, make(chan T), make(chan T, 0)
Unbuffered channel
Unbuffered channels are also called synchronous channels, and communications will only succeed if both the sender and the receiver are ready.
Synchronization operation example:
func ChannelSync() { // Initialize the datach := make(chan int) wg := {} // Send interval (1) go func() { defer () for i := 0; i < 5; i++ { ch <- i println("Send ", i, ".\tNow:", ().Format("15:04:05.999999999")) // Interval time (1 * ) } close(ch) }() // Interval reception (1) go func() { defer () for v := range ch { println("Received ", v, ".\tNow:", ().Format("15:04:05.999999999")) // Interval time, note that the interval time is different from send (3 * ) } }() () }
Execution results:
Send 0 . Now: 17:54:27.772773
Received 0 . Now: 17:54:27.772795
Received 1 . Now: 17:54:30.773878
Send 1 . Now: 17:54:30.773959
Received 2 . Now: 17:54:33.775132
Send 2 . Now: 17:54:33.775208
Received 3 . Now: 17:54:36.775816
Send 3 . Now: 17:54:36.775902
Received 4 . Now: 17:54:39.776408
Send 4 . Now: 17:54:39.776456
In the code, a synchronous channel is used, and two goroutines are used to complete the sending and receiving. The time intervals for each sending and receiving are different. We print the sent and received values and times respectively. You can see the execution results: the sending and receiving time is the same; the interval is based on the long one, and it can be seen that the sending and receiving operations are synchronous operations. Therefore, synchronous channel is suitable for synchronous signals between gotoutines
Buffer Channel
Buffered Channel is also called asynchronous Channel, and the receiver and sender can succeed without waiting for both parties to be ready. There will be a buffer space with cap capacity in the buffer Channel. When using buffered Channel communication, the receive and send operations are operating the Channel's Buffer, which is a typical queue operation:
- When receiving, the element is received from the buffer, as long as the buffer is not empty, it will not block. On the contrary, the buffer is empty, which will block and the goroutine hangs
- When sending, send elements to the buffer, and as long as the buffer is not full, it will not block. On the contrary, if the buffer is full, it will block and the goroutine will hang
Operation example:
func main() { // Initialize the data ch := make(chan int, 5) wg := {} // Send interval (1) go func() { defer () for i := 0; i < 5; i++ { ch <- i println("Send ", i, ".\tNow:", ().Format("15:04:05.999999999")) // Interval time (1 * ) } }() // Interval reception (1) go func() { defer () for v := range ch { println("Received ", v, ".\tNow:", ().Format("15:04:05.999999999")) // Interval time, note that the interval time is different from send (3 * ) } }() () }
Execution results:
Send 0 . Now: 17:59:32.990698
Received 0 . Now: 17:59:32.99071
Send 1 . Now: 17:59:33.992127
Send 2 . Now: 17:59:34.992832
Received 1 . Now: 17:59:35.991488
Send 3 . Now: 17:59:35.993155
Send 4 . Now: 17:59:36.993445
Received 2 . Now: 17:59:38.991663
Received 3 . Now: 17:59:41.99184
Received 4 . Now: 17:59:44.992214
In the code, it is consistent with the synchronous channel, but a buffer channel with a capacity of 5 is used, and two goroutines are used to complete the sending and receiving. The time intervals for each sending and receiving are different. We print the sent and received values and times respectively. You can see the execution results: the sending and receiving time is different; the sending and receiving operations will not block, and it can be seen that the sending and receiving operations are asynchronous operations. Therefore, buffered channels are very suitable for data communication between goroutines
Channel principle
Source code
In the runtime/ in the source code package, you can see the channel implementation source code:
type hchan struct { qcount uint // The number of elements is obtained by len() dataqsiz uint // The length of the buffer queue, that is, the capacity is obtained through cap() buf // Buffer queue pointer, unbuffered queue is nil elemsize uint16 // Element size closed uint32 // Close sign elemtype \*\_type // Element type sendx uint // Send element index recvx uint // Receive element index recvq waitq // Receive Goroutinue queue sendq waitq // Send Goroutinue queue // 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 // Lock}
buf can be understood as a ring array used to cache elements in the Channel. Why use ring arrays instead of ordinary arrays? Because ordinary arrays are more suitable for the specified space, when elements are popped up, all ordinary arrays need to be moved forward, and using ring arrays + subscript indexes can achieve efficient reading and writing of data without moving elements.
Sendx and recvx will return to the first position when the subscript exceeds the array capacity, so there are two fields that need to record the current read and written subscript positions.
recvq and sendq are used to record the goroutine queue waiting for reception and transmission. When the received or sent goroutine based on a certain channel cannot be understood and executed, that is, when blocking is required, it will be recorded in the wait queue of the Channel. When the channel can complete the corresponding reception or send operation, the goroutine is awakened from the waiting queue to perform operations.
The waiting queue is actually a two-way linked list structure
life cycle
Create a policy
- Direct allocation of memory without buffering
- The buffered one does not contain pointers, assigns contiguous addresses to hchan and underlying arrays
- A buffered channel and contains element pointers, which will assign addresses to hchan and underlying arrays.
Send policy
- Convert to function when sending operation is compiled
- Blocking: block=true; non-blocking: block=false
- Sending data to the channel is divided into two parts: inspection and data transmission, data transmission:
- If the channel's read wait queue exists for the recipient goroutinue
- Send data directly to the first waiting goroutinue to wake up the received goroutinue
- If the channel read waiting queue does not exist for the receiver goroutinue
- If the loop array buf is not full, the data is sent to the end of the loop array buf
- If the loop array buf is full, the sending process will be blocked, the current goroutinue will be added to the write waiting queue, and the waiting waiting to wake up will be suspended
- If the channel's read wait queue exists for the recipient goroutinue
Receive Policy
- The receive operation is converted to a function
- Blocking: block=true; non-blocking: block=false
- Receive data from channel:
- If the write waiting queue of channel exists as a sender goroutinue:
- If it is an unbuffered channel, copy the data directly from the first sender goroutinue to the receiving variable, and wake up the sent goroutinue
- If there is a buffer channel (full), copy the queue element of the loop array buf to the receiving variable, copy the data of the first sender goroutinue to the buf loop array, and wake up the sent goroutinue
- If the write wait for channel does not exist for sender goroutinue
- If the loop array buf is not empty, copy the header element of the loop array buf to the receiving variable
- If the loop array buf is empty, the reception process will be blocked, and the current goroutine will be added to the read waiting queue and the waiting waiting to wake up will be suspended.
- If the write waiting queue of channel exists as a sender goroutinue:
closure
Calling a function
A simple explanation of some basic usage and principles of Channel is given. You can write more concurrent code and read the source code to deepen your understanding of Channel.
The above is the detailed explanation of Channel, an important component that Golang cannot avoid, for concurrency. For more information about Golang Channel, please pay attention to my other related articles!