go channel axiom

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 个通用属性:

  • nilchannel 发消息会阻塞(A send to a nil channel blocks forever)
  • nilchannel 接受消息会阻塞(A receive from a nil channel blocks forever)
  • closedchannel 发消息会触发恐慌(A send to a closed channel panics)
  • closedchannel 接受消息会马上收到零(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 is nil 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

扩展阅读

相关阅读:

  1. Curious Channels
  2. Wednesday pop quiz: spot the race
  3. Never start a goroutine without knowing how it will stop
  4. Struct composition with Go
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容