Most new Go programmers quickly grasp the idea of a channel as a queue of values and are comfortable with the notion that channel operations may block when full or empty.
大部分新手 Go 程序员已经理解了 channel 是队列,队列是满或空队列会阻塞感到满意。
This post explores four of the less common properties of channels:
channel 有如下 4 个通用属性:
- 向
nil
的channel
发消息会阻塞(A send to a nil channel blocks forever) - 从
nil
的channel
接受消息会阻塞(A receive from a nil channel blocks forever) - 向
closed
的channel
发消息会触发恐慌(A send to a closed channel panics) - 从
closed
的channel
接受消息会马上收到零(A receive from a closed channel returns the zero value immediately)
A send to a nil channel blocks forever
The first case which is a little surprising to newcomers is a send on a nil
channel blocks forever.
第一个场景会让新手惊讶,向 nil
的 channel 发消息会阻塞
This example program will deadlock on line 5 because the zero value for an uninitalised channel is nil
.
这个程序将在第五行产生死锁,因为未初始化的 channel 是 nil
package main
func main() {
var c chan string
c <- "let's get started" // deadlock
}
http://play.golang.org/p/1i4SjNSDWS
A receive from a nil channel blocks forever
Similarly receiving from a nil
channel blocks the receiver forever.
类似的从nil
的 channel 接受消息会发生阻塞。
package main
import "fmt"
func main() {
var c chan string
fmt.Println(<-c) // deadlock
}
http://play.golang.org/p/tjwSfLi7x0
So why does this happen ? Here is one possible explanation
为什么会这样?这里有些解释
- 缓冲 channel 不是type的一部分,而是 channel 值的一部分。(The size of a channel’s buffer is not part of its type declaration, so it must be part of the channel’s value.
- 如果 channel 未初始化,则长度是零(If the channel is not initalised then its buffer size will be zero.)
- 如果长度是零,则 channel 是非缓冲(If the size of the channel’s buffer is zero, then the channel is unbuffered.)
- 如果 channel 是非缓冲,则发消息会阻塞直到有接受者为止(If the channel is unbuffered, then a send will block until another goroutine is ready to receive.)
- 如果 channel 是
nil
那么发送者和接受者都不会互相引用;两者阻塞在独立的 channel 并永远不会解除阻塞。(If the channel isnil
then the sender and receiver have no reference to each other; they are both blocked waiting on independent channels and will never unblock.)
A send to a closed channel panics
The following program will likely panic as the first goroutine to reach 10 will close the channel before its siblings have time to finish sending their values.
这个程序会触发 panic,第一个 goroutine 会遍历 10 次后关闭,其他 goroutine 在关闭前有时间发送消息。
package main
import "fmt"
func main() {
var c = make(chan int, 100)
for i := 0; i < 10; i++ {
go func() {
for j := 0; j < 10; j++ {
c <- j
}
close(c)
}()
}
for i := range c {
fmt.Println(i)
}
}
http://play.golang.org/p/hxUVqy7Qj-
So why isn’t there a version of close()
that lets you check if a channel is closed ?
那么为什么没有一个close()
去检查,如果 channel 被关闭了?
if !isClosed(c) {
// c isn't closed, send the value
c <- v
}
But this function would have an inherent race. Someone may close the channel after we checked isClosed(c)
but before the code gets to c <- v
.
但这个函数有个内部竞争。在检查isClosed(c)
之后,且 c <- v
之前,关闭 channel
Solutions for dealing with this fan in problem are discussed in the 2nd article linked at the bottom of this post.
解决方案这里
A receive from a closed channel returns the zero value immediately
The final case is the inverse of the previous. Once a channel is closed, and all values drained from its buffer, the channel will always return zero values immediately.
最后一个场景和前一个相反。一个已关闭且所以值消耗完,这个 channel 将一直返回零
package main
import "fmt"
func main() {
c := make(chan int, 3)
c <- 1
c <- 2
c <- 3
close(c)
for i := 0; i < 4; i++ {
fmt.Printf("%d ", <-c) // prints 1 2 3 0
}
}
http://play.golang.org/p/ajtVMsu8EO
The correct solution to this problem is to use a for range
loop.
正确的解决方法是使用for range
循环
for v := range c {
// do something with v
}
for v, ok := <- c; ok ; v, ok = <- c {
// do something with v
}
These two statements are equivalent in function, and demonstrate what for range
is doing under the hood.
这两个语句在功能上是等价的,并且演示了for range
在幕后所做的事情。
总结
channel 有 3 个状态,有 3 中操作,共 9 个结果,总结如下:
操作(行)\状态(列) | nil | closed | active |
---|---|---|---|
close | panic | panic | closed |
send | block | panic | success |
receive | block | zero | success |
扩展阅读
- 原文翻译 原文地址
- Concurrency is not Parallelism by Rob Pike
- Go Concurrency Patterns: Pipelines and cancellation
- Curious Channels