我正在努力更好地了解golang频道。在阅读this article时,我正在使用非阻塞发送,并提出以下代码:
package main
import (
"fmt"
"time"
)
func main() {
stuff := make(chan int)
go func(){
for i := 0; i < 5; i ++{
select {
case stuff <- i:
fmt.Printf("Sent %v\n", i)
default:
fmt.Printf("Default on %v\n", i)
}
}
println("Closing")
close(stuff)
}()
time.Sleep(time.Second)
fmt.Println(<-stuff)
fmt.Println(<-stuff)
fmt.Println(<-stuff)
fmt.Println(<-stuff)
fmt.Println(<-stuff)
}
这将打印:
Default on 0
Default on 1
Default on 2
Default on 3
Default on 4
Closing
0
0
0
0
0
虽然我明白只会打印0
,但我真的不明白为什么第一次发送仍会触发选择的default
分支?
在这种情况下,选择行为背后的逻辑是什么?
答案 0 :(得分:4)
您永远不会向stuff
发送任何值,在执行fmt.Println
语句中的任何接收操作之前执行所有默认情况。如果没有其他操作可以继续,则立即采用default
情况,这意味着您的循环将尽快执行并返回。
您想要阻止循环,因此您不需要default
案例。您最后也不需要close
,因为您不依赖于已关闭的频道取消阻止接收或违反range
子句。
stuff := make(chan int)
go func() {
for i := 0; i < 5; i++ {
select {
case stuff <- i:
fmt.Printf("Sent %v\n", i)
}
}
println("Closing")
}()
time.Sleep(time.Second)
fmt.Println(<-stuff)
fmt.Println(<-stuff)
fmt.Println(<-stuff)
fmt.Println(<-stuff)
fmt.Println(<-stuff)
https://play.golang.org/p/k2rmRDP38f
另请注意,最后的“已发送”和“结束”行未打印,因为您没有其他同步等待goroutine完成,但这不会影响此示例的结果。
答案 1 :(得分:2)
它只执行默认情况,因为for循环在从通道开始读取任何内容之前运行5次。每次通过,因为没有任何东西可以从通道读取,它将进入默认情况。如果某些东西可以从频道中读取,它就会执行该情况。
答案 2 :(得分:2)
由于您正在使用非阻止&#39;发送&#39;,如果读者已经在等待阅读频道上的内容,stuff <- i
将真正执行,或者如果通道有一些缓冲区。如果没有,请发送&#39;必须阻止。
现在,因为在从通道读取的打印语句之前有time.Sleep(time.Second)
,所以直到1秒后才会有通道的读者。另一方面,goroutine在那段时间内完成执行,并且不会发送任何内容。
您在输出中看到全部为零,因为fmt.Println(...)
语句正在从已关闭的频道中读取。
答案 3 :(得分:1)
你的第一个案子没有执行。
以下是您的计划的作用:
0
至4
,这些频道全部阻止,因为没有任何内容正在阅读频道,因此会陷入默认状态。0
。要获得理想的行为,您有两种选择:
使用缓冲通道,它可以保存您发送的所有数据:
stuff := make(chan int, 5)
不要在select语句中使用default
,这会导致每个发送都等到它成功。
哪个首选取决于您的目标。对于像这样的最小例子,要么可能没有更好或更差。