为什么当第一个case实际执行时,这个选择语句总是运行默认的case?

huangapple go评论97阅读模式
英文:

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语句的行为逻辑是什么?

在Go Playground上的示例

英文:

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 (
	&quot;fmt&quot;
	&quot;time&quot;
)

func main() {
	stuff := make(chan int)
	go func(){
		for i := 0; i &lt; 5; i ++{
			select {
			case stuff &lt;- i:
				fmt.Printf(&quot;Sent %v\n&quot;, i)
			default:
				fmt.Printf(&quot;Default on %v\n&quot;, i)
			}
		}
		println(&quot;Closing&quot;)
		close(stuff)
	}()
	time.Sleep(time.Second)
	fmt.Println(&lt;-stuff)
	fmt.Println(&lt;-stuff)
	fmt.Println(&lt;-stuff)
	fmt.Println(&lt;-stuff)
	fmt.Println(&lt;-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 0s 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?

Example at the Go Playground

答案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 &lt; 5; i++ {
		select {
		case stuff &lt;- i:
			fmt.Printf(&quot;Sent %v\n&quot;, i)
		}
	}
	println(&quot;Closing&quot;)
}()
time.Sleep(time.Second)
fmt.Println(&lt;-stuff)
fmt.Println(&lt;-stuff)
fmt.Println(&lt;-stuff)
fmt.Println(&lt;-stuff)
fmt.Println(&lt;-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 &lt;- 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

你的第一个案例没有执行。

以下是你的程序的执行过程:

  1. 启动一个 goroutine。
  2. 尝试通过通道发送 04,但由于没有任何读取通道的操作,所有的发送操作都会阻塞,然后执行 default 分支。
  3. 同时,在主 goroutine 中,你在睡眠一秒钟...
  4. 一秒钟过后,尝试从通道中读取数据,但通道已关闭,所以每次都会得到 0

要实现你期望的行为,你有两个选择:

  1. 使用带缓冲的通道,可以容纳你发送的所有数据:

     stuff := make(chan int, 5)
    
  2. 在 select 语句中不使用 default 分支,这样每次发送操作都会等待直到成功。

哪种方式更好取决于你的目标。对于这样一个简单的示例,两种方式可能都没有更好或更差的选择。

英文:

Your first case isn't executing.

Here's what your program does:

  1. Start a goroutine.
  2. Attempt to send 0 through 4 on the channel, which all block, because there is nothing reading the channel, so fall through to the default.
  3. Meanwhile, in the main goroutine, you're sleeping for one second...
  4. 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:

  1. Use a buffered channel, which can hold all of the data you send:

     stuff := make(chan int, 5)
    
  2. 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.

huangapple
  • 本文由 发表于 2017年8月2日 03:43:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/45446429.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定