go nil channels
ddatsh
package main
import "fmt"
func main() {
a := make(chan int)
b := make(chan int)
go func() {
for i := 0; i < 5; i++ {
a <- i
}
close(a)
}()
go func() {
for i := 0; i < 15; i++ {
b <- i
}
close(b)
}()
for {
select {
case v := <-a:
fmt.Println("receive from a", v)
case v := <-b:
fmt.Println("receive from b", v)
}
}
}
创建两个 goroutines,在 channels a
和 b
上发送消息。发送完值后,关闭 channel
main routine无限循环来消耗channel中的值。不关心处理的顺序,使用 for..select处理来自channel的值
打印从通道获得的值后,无限循环继续运行。循环继续打印来自两个通道 b 和 a 的 0 值
一旦channel关闭,并且所有值都从其缓冲区中耗尽,通道将始终立即返回零值
receive from b 0
receive from b 1
receive from a 0
receive from a 1
receive from b 2
receive from b 3
receive from a 2
receive from a 3
receive from a 4
receive from a 0
receive from b 4
receive from b 5
receive from a 0
receive from b 6
receive from b 7
receive from a 0
receive from b 8
receive from a 0
receive from b 9
receive from a 0
receive from a 0
receive from a 0
receive from a 0
receive from b 10
receive from a 0
receive from b 11
receive from a 0
receive from b 12
receive from a 0
receive from a 0
receive from b 13
receive from a 0
receive from b 14
receive from a 0
receive from a 0
receive from b 0
receive from a 0
...
for {
select {
case v, ok := <-a:
if !ok {
fmt.Println("channel a closed")
continue
}
fmt.Println("receive from a", v)
case v, ok := <-b:
if !ok {
fmt.Println("channel b closed")
continue
}
fmt.Println("receive from b", v)
}
}
通过从channel中获取开关ok值来了解通道何时关闭
虽然停止打印 0 值,但仍在关闭了的channel上无限迭代并打印相同的值
Channel a is closed
Channel a is closed
Channel b is closed
Channel b is closed
...
通过aClosed bClosed两个变量,每当知道channel关闭时,尝试打破循环
aClosed, bClosed := false, false
for !aClosed || !bClosed {
select {
case v, ok := <-a:
if !ok {
aClosed = true
fmt.Println("channel a closed")
continue
}
fmt.Println("receive from a", v)
case v, ok := <-b:
if !ok {
bClosed = true
fmt.Println("channel b closed")
continue
}
fmt.Println("receive from b", v)
}
}
能够不再在 for 循环中无限运行。但仍然会多次打印 Channel a is closed
receive from b 0
receive from a 0
receive from a 1
receive from b 1
receive from a 2
receive from a 3
receive from a 4
channel a closed
channel a closed
channel a closed
channel a closed
receive from b 2
channel a closed
channel a closed
receive from b 3
receive from b 4
channel a closed
channel a closed
channel a closed
receive from b 5
receive from b 6
channel a closed
receive from b 7
channel a closed
receive from b 8
receive from b 9
receive from b 10
channel a closed
channel a closed
channel a closed
channel a closed
channel a closed
channel a closed
receive from b 11
channel a closed
channel a closed
channel a closed
receive from b 12
channel a closed
channel a closed
channel a closed
receive from b 13
receive from b 14
channel b closed
当使用具有 select 多个channel操作的语句时,如果其中一个channel关闭,select 立即解析该channel,加上每次 select 都可能选择已关闭通道的case,*因此,看到 Channel a is closed 被打印了多次
为避免此问题,一旦通道关闭,应该从select中移除对应通道的case,由于select 无法动态移除case,可以通道设置 nil channel实现类似效果(go 从 nil channel接收或发送会永远阻塞,所以select 将不会选择那个case)
aClosed, bClosed := false, false
for !aClosed || !bClosed {
select {
case v, ok := <-a:
if !ok {
aClosed = true
a = nil
fmt.Println("channel a closed")
continue
}
fmt.Println("receive from a", v)
case v, ok := <-b:
if !ok {
bClosed = true
b = nil
fmt.Println("channel b closed")
continue
}
fmt.Println("receive from b", v)
}
}
receive from b 0
receive from b 1
receive from b 2
receive from a 0
receive from b 3
receive from a 1
receive from a 2
receive from b 4
receive from a 3
receive from b 5
receive from b 6
receive from a 4
receive from b 7
channel a closed
receive from b 8
receive from b 9
receive from b 10
receive from b 11
receive from b 12
receive from b 13
receive from b 14
channel b closed