英文:
Why does this select always run the default case when the first case actually is executed?
问题
我正在尝试更好地理解Go语言的通道(channels)。在阅读这篇文章时,我正在尝试非阻塞发送,并编写了以下代码:
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
会被打印出来,但我真的不明白为什么第一次发送仍然会触发select
语句中的default
分支。
在这种情况下,select
语句的行为逻辑是什么?
英文:
I'm trying to get a better understanding of golang channels. While reading this article I'm toying with non-blocking sends and have come up with the following code:
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)
}
This will print:
Default on 0
Default on 1
Default on 2
Default on 3
Default on 4
Closing
0
0
0
0
0
While I do understand that only 0
s will get printed I do not really understand why the first send does still trigger the default
branch of the select?
What is the logic behind the behavior of a select in this case?
答案1
得分: 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)
请注意,最后的"Sent"和"Closing"行没有被打印出来,因为你没有其他同步等待goroutine完成,但这不会影响此示例的结果。
链接:https://play.golang.org/p/k2rmRDP38f
英文:
You never send any values to stuff
, you execute all the default cases before you get to any of the receive operations in the fmt.Println
statements. The default
case is taken immediately if there is no other operation than can proceed, which means that your loop will execute and return as quickly as possible.
You want to block the loop, so you don't need the default
case. You don't need the close
at the end either, because you're not relying on the closed channel unblocking a receive or breaking from a range
clause.
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
Notice also that the last "Sent" and the "Closing" line aren't printed, because you have no other synchronization waiting for the goroutine to finish, however that doesn't effect the outcome of this example.
答案2
得分: 4
由于您正在使用非阻塞的 send
,所以 stuff <- i
只有在已经有读取者等待在通道上读取内容,或者通道有一些缓冲区时才会真正执行。如果没有,send
将会阻塞。
现在,由于在从通道读取的打印语句之前有一个 time.Sleep(time.Second)
,在经过1秒后才会有读取者读取通道。而另一方面,goroutine 在这段时间内完成执行,并且没有发送任何内容。
您在输出中看到全为零,是因为 fmt.Println(...)
语句正在从一个已关闭的通道中读取。
英文:
Since you're using a non-blocking 'send', the stuff <- i
will really only be executed if there's a reader already waiting to read things on the channel, or if the channel has some buffer. If not, the 'send' would have to block.
Now since you have a time.Sleep(time.Second)
before the print statements that read from the channel, there are no readers for the channel till after 1 second has passed. The goroutine on the other hand finishes executing within that time and doesn't send anything.
You're seeing all zeroes in the output because the fmt.Println(...)
statements are reading from a closed channel.
答案3
得分: 3
你的第一个案例没有执行。
以下是你的程序的执行过程:
- 启动一个 goroutine。
- 尝试通过通道发送
0
到4
,但由于没有任何读取通道的操作,所有的发送操作都会阻塞,然后执行 default 分支。 - 同时,在主 goroutine 中,你在睡眠一秒钟...
- 一秒钟过后,尝试从通道中读取数据,但通道已关闭,所以每次都会得到
0
。
要实现你期望的行为,你有两个选择:
-
使用带缓冲的通道,可以容纳你发送的所有数据:
stuff := make(chan int, 5)
-
在 select 语句中不使用 default 分支,这样每次发送操作都会等待直到成功。
哪种方式更好取决于你的目标。对于这样一个简单的示例,两种方式可能都没有更好或更差的选择。
英文:
Your first case isn't executing.
Here's what your program does:
- Start a goroutine.
- Attempt to send
0
through4
on the channel, which all block, because there is nothing reading the channel, so fall through to the default. - Meanwhile, in the main goroutine, you're sleeping for one second...
- Then after the second has elapsed, attempt to read from the channel, but it is closed, so you get
0
every time.
To get your desired behavior, you have two choices:
-
Use a buffered channel, which can hold all of the data you send:
stuff := make(chan int, 5)
-
Don't use
default
in your select statement, which will cause each send to wait until it can succeed.
Which is preferred depends on your goals. For a minimal example like this, either is probably no better or worse.
答案4
得分: 2
只有执行默认情况,因为在任何东西开始从通道读取之前,for循环会运行5次。每次循环,因为没有东西可以从通道读取,它都会进入默认情况。如果有东西可以从通道读取,它将执行该情况。
英文:
It only executes the default case, because the for loop runs 5 times before anything starts reading from the channel. Each time through, because nothing can read from the channel, it goes to the default case. If something could read from the channel, it would execute that case.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论