Go|踩坑不完全记录之channel

channel 是 golang 并发编程的极其重要的组成部分之一。但是关于 channel 的一些特性,如果不熟记于心,那么,可是会陷入莫名其妙的问题中无法自拔。下面,是关于 channel 你可能会踩到的雷……

当 channel 为 nil 时…

当你声明一个 channel 并且不进行任何初始化动作的时候,恭喜你,你就获得了一个 nil channel:

1
var c chan struct{}

在 golang 中,从一个 nil channel 接收数据,以及往一个 nil channel 发送数据都会天荒地老地阻塞。

这看起来并没有什么。但是,地雷暗藏其中。

举个栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import "fmt"
import "time"

func main() {
var c chan bool
go func(){
for d := range c {
fmt.Println("receive a value: ", d)
}
}()
time.Sleep(3*time.Second)
fmt.Println("sleep for a moment and initialize channel c")
c = make(chan bool, 1)
c <- true
fmt.Println("send true to channel")
time.Sleep(3*time.Second)
fmt.Println("Done")
}

在上面的代码中,我们声明(但是并未初始化)了一个 channel c。然后,创建一个 goroutine 来消费 channel c,并将从中接收到的数据打印出来。等待 3 秒后再对 channel c 进行初始化。接着,往该 channel 发送数据并再度等待 3 秒。

对于上面的代码,我们期望的输出是:

1
2
3
4
sleep for a moment and initialize channel c
send true to channel
receive a value: true
Done

但是实际上运行上面代码得到的结果是:

1
2
3
sleep for a moment and initialize channel c
send true to channel
Done

在 go 的 for…range 循环中,使用值拷贝的方式代替被遍历元素本身。在上面的代码中,channel c 尚未初始化就在创建的 goroutine 中使用了,所以实际上使用 for…range 的对象是个 nil channel,因此对其的读取操作会永远阻塞。

将初始化放在读取之前,就能获得我们期望的结果。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import "fmt"
import "time"

func main() {
var c chan bool
c = make(chan bool, 1)
go func(){
for d := range c {
fmt.Println("receive a value: ", d)
}
}()
time.Sleep(3*time.Second)
fmt.Println("sleep for a moment and initialize channel c")
//c = make(chan bool, 1)
c <- true
fmt.Println("send true to channel")
time.Sleep(3*time.Second)
fmt.Println("Done")
}

参考