This is a creation in Article, where the information may have evolved or changed.
The Go language channel has a seemingly odd feature, which is that if you write to or read data to a channel that is null (nil), the current goroutine will block forever. For example
func main() {var ch chan intch <- 1 // block forerver}func main() {var ch chan int<-ch // block forerver}func main() {<-chan int(nil) // block forerver}func main() {chan int(nil)<-1 // block forerver}
The above four main functions will always block (but because there is no other goroutine, runtime will report a deadlock error).
This seems to be a bug because it doesn't make sense to read or write to a channel that is not initialized. But why would go team design this?
There is a lot of discussion on this issue in Golang-nuts, and one group of discussions [1] says that this feature (that is, permanent blocking of nil channel reads and writes) can be used to gracefully implement a pattern called "guarded selective wating". In fact, the condition waits: In some cases in select, if the corresponding conditions are not satisfied, you will not wait on this case. Suppose there is a requirement for such a condition to wait:
select {case <-chan_a: // 希望cond_a为真时才在chan_a上等待// do somethingcase <-chan_b: // 希望cond_b为真时才在chan_b上等待// do somethingcase <-chan_def:// do something}
This pattern can be implemented without the use of this feature, but the code is more verbose and unsightly. One of the implementations might be this:
switch {case cond_a && cond_b:select {case <-chan_a:// do somethingcase <-chan_b:// do somethingcase <-chan_def:// do something}case !cond_a && cond_b:select {case <-chan_b:// do somethingcase <-chan_def:// do something}case cond_a && !cond_b:select {case <-chan_a:// do somethingcase <-chan_def:// do something}default:select {case <-chan_def:// do something}}
As you can see, implementing code is tedious and verbose and error-prone. And if the case branch is more, the number of lines that implement the code will explode with a 2 exponential increase. Of course there are other ways to implement, but if you do not try to deliberately block a channel, the implementation of the method is very similar, there are the above-mentioned problems.
But with the Nil channel feature, the implementation can be very elegant and concise:
maybe := func(flag bool, ch chan int) <-chan int {if flag {return ch}return nil}select {case <- maybe(cond_a, chan_a):// do somethingcase <- maybe(cond_b, chan_b):// do somethingcase <- chan_def:// do something}
This is actually using the nil channel forever blocking feature, but if we create a channel, but do not write to it or close it, but only read the data from it, it can also be implemented forever blocking. The following code achieves the same effect:
var _BLOCK = make(<-chan int)maybe := func(flag bool, ch chan int) <-chan int {if flag {return ch}return _BLOCK}select {case <- maybe(cond_a, chan_a):// do somethingcase <- maybe(cond_b, chan_b):// do somethingcase <- chan_def:// do something}
This means that the persistent blocking feature of the nil channel is not necessary for implementing a "guarded selective wating" mode because there are alternative implementations. However, it is clear that the nil channel is more convenient and does not require additional waste of resources to create a channel for permanent blocking.
some controversies:
Some people say that even if the nil channel is useful in select, it is a strange behavior to read and write a nil channel in addition to the Select, which in any case looks like a bug. Runtime If you create a panic here instead of permanently blocking, you can better tell the programmer, "Hey, you have a bug here." If it is permanently blocked, the bug will not be so easily noticed.
Members of the go team responded by saying: This is in line with the behavior in select, and if the nil channel is permanently blocked in select and panic elsewhere, the behavior is inconsistent, confusing the programmer, and it violates the GO1 language specification; Read nil The channel is permanently blocked, and it is the same as reading a channel without data, like traversing an array of empty values or a map and traversing a 0-length array slice or a map effect with no members.
For example:
var s []int // 未初始化,s是一个空值for k, v := range s {// do something}
And
s := []int{} // 已初始化,s长度为0for k, v := range s {// do something}
These two code behaviors are the same, the code in the loop body will not be executed, nor will it be panic.
My view is that the read-write nil channel outside of select is indeed a bug, at least a bad code style (if someone is deliberately doing so). But it is not easy to show up in practice, because when we use the channel we usually put the declaration and initialization together, so it is not NULL, or the channel is a member of a struct, declaration and initialization is separate, but there is usually a function to initialize the structure. So in the actual coding is not easy to read and write a nil channel bug, this is not a serious problem.
[1]https://groups.google.com/forum/?fromgroups=#!topic/golang-nuts/chpxr_h8kum