This is a creation in Article, where the information may have evolved or changed.
The channel is often used in the process of writing Golang programs. The use of channel is indeed very special, but also very practical.
The original is here: Http://dave.cheney.net/2013/04/30/curious-channels
———— Translation Divider Line ————
Wonderful channel.
In the Go programming language, the channel is a shining feature. It provides a powerful way to send a stream of data from one goroutine to another goroutine without using a lock or critical section.
Today I would like to discuss two important features of channel, which are not only very useful in controlling data flow, but also effective in process control.
A channel that has been closed will never block.
First feature, I would like to talk about the channel that has been closed. Once a channel is closed, it is no longer possible to send data to the channel, but you can still try to get the value from the channel.
Package Mainimport "FMT" Func Main () { ch: = Make (chan bool, 2) ch <-true ch <-true Close (CH) F or I: = 0; I < Cap (CH) +1; i++ { V, OK: = <-ch fmt. Println (V, OK) }}
In this example, we create a channel with a buffer of two values, fill the buffer, and close it off.
True Truetrue TrueFalse False
Executing this program will first show us the two values sent to the channel, and then the third attempt on the channel will return flase and false. The first false is the 0 value of the channel type, the type of channel is Chan bool, and that is false. The second indicates the enabled state of the channel, which is currently false, indicating that the channel is closed. The channel will always return these values. As an attempt, you can modify this example to take a 100-time value from the channel.
The ability to detect if the channel is closed is a useful feature that can be used to range the channel and exit the loop when the channel is emptied.
Package Mainimport "FMT" Func Main () { ch: = Make (chan bool, 2) ch <-true ch <-true Close (CH) F or V: = range ch { FMT. Println (v)//was called two times }}
But its true value is embodied when combined with select. Let's start with this example.
Package Mainimport ( "FMT" "Sync", "Time ") func main () { Finish: = Make (chan bool) var do sync. Waitgroup done . ADD (1) go func () { Select {case <-time. After (1 * time. Hour): Case <-finish: } done . Done () } () t0: = time. Now () finish <-true//Send off signal done . Wait () //waits for Goroutine to end the fmt. Printf ("Waited%v for Goroutine to Stop\n", time. Since (t0))}
On my system, this program runs with a very short wait delay, so it's clear that Goroutine won't wait a full one hours and then call done. Done ()
Waited 129.607us for Goroutine to stop
But there are some problems in this program. The first is that the finish channel is not buffered, so if the receiver forgets to add finish to its SELECT statement, sending the data to it may result in blocking. The select block to be sent can be encapsulated to ensure that it is not blocked or that the finish channel is buffered to resolve the problem. However, if many goroutine are listening on the finish channel, you need to track the situation and remember to send the correct amount of data to the finish channel. It can be tricky to control the creation of Goroutine, and they may also be created by another part of the program, such as in response to a network request.
A good solution to this problem is to use the channel that has been closed to return this mechanism in real time. Using this feature to rewrite the program, now contains 100 goroutine, without the need to track the number of goroutine generated, or adjust the size of the finish channel.
Package Mainimport ( "FMT" "Sync" "Time ") func main () { Const n = + : = Make (chan bool) var done sync. Waitgroup for I: = 0, i < n; i++ {done . ADD (1) go func () { Select {case <-time. After (1 * time. Hour): Case <-finish: } done . Done () } () } T0: = time. Now () Close (finish) //closes finish so that it returns done immediately . Wait () //waits for all goroutine to end the fmt. Printf ("Waited%v for%d goroutines to Stop\n", time. Since (t0), N)}
On my system, it returns
Waited 231.385us for Goroutines to stop
So what's going on here? When the finish channel is closed, it returns immediately. Then all waits to receive time. The SELECT statement for the after channel or finish Goroutine is completed immediately, and Goroutine is called done. Done () to reduce the Waitgroup counter after exiting. This powerful mechanism makes it possible to send signals to them without having to know any details of the unknown number of goroutine, without worrying about deadlocks.
Before moving on to the next topic, take a look at a few simple examples that many Go programmers love. In the example above, the data is never sent to the finish channel, and any data received by the receiving party is discarded. So it's normal to write a program like this:
Package Mainimport ( "FMT" "Sync", "Time ") func main () { Finish: = Make (chan struct{}) var do sync. Waitgroup done . ADD (1) go func () { Select {case <-time. After (1 * time. Hour): Case <-finish: } done . Done () } () t0: = time. Now () Close (finish) is done . Wait () FMT. Printf ("Waited%v for Goroutine to Stop\n", time. Since (t0))}
When close relies on the message mechanism to close the channel, and no data is sent and received, the definition of finish as type Chan struct{} means that the channel has no data and is only interested in its closed properties.
A nil channel is always blocked.
The second feature I want to talk about is exactly the opposite of the characteristic of a channel that has been closed. A nil channel; When the value of the channel has not been initialized or assigned to nil, it is always blocked. For example
Package MainFunc Main () { var ch chan bool ch <-true//Forever blocked}
When CH is nil, it will be deadlocked and will never send data. For the reception is the same
Package MainFunc Main () { var ch chan bool <-CH//Forever blocked}
This may not seem very important, but it is really a useful feature when using the closed channel mechanism to wait for multiple channel closures. For example
Waitmany wait for A and b to close. Func Waitmany (A, B chan bool) { var aclosed, bclosed bool for !aclosed | |!bclosed { Select {case <-a: aclosed = True case <-b: bclosed = True }} }
Waitmany () is a good way to wait for channel A and b to close, but there is a problem. Suppose channel A is closed first, and then it returns immediately. But since bclosed is still false, the program goes into a dead loop, and Channel B is never judged to be closed.
A safe way to solve this problem is to take advantage of the blocking characteristics of the nil channel and rewrite the program as follows
Package Mainimport ( "FMT" "Time") func Waitmany (A, B chan bool) { for a! = Nil | | b! = NIL { Select { Case <-A: a = nil case <-b: b = Nil } }}func Main () { A, B: = yes (chan bool), make (Chan Bo OL) T0: = time. Now () go func () { Close (a) close (b) } () Waitmany (A, b) FMT. Printf ("Waited%v for waitmany\n", time. Since (t0))}
In the overridden Waitmany (), once a value is received, the reference to a or B is set to nil. When the nil channel is part of a SELECT statement, it is actually ignored, so setting a to nil removes it from select, leaving only b waiting for it to be closed, and then exiting the loop.
Run on my system to get
Waited 54.912us for Waitmany
In summary, it is close and nil channle channel These features are very simple, making them a powerful component for creating highly concurrent programs.