m90
m90

Reputation: 11822

Why does this select always run the default case when the first case actually is executed?

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 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

Upvotes: 8

Views: 3125

Answers (4)

Jonathan Hall
Jonathan Hall

Reputation: 79566

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.

Upvotes: 3

Mr_Pink
Mr_Pink

Reputation: 109347

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.

Upvotes: 5

svsd
svsd

Reputation: 1869

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.

Upvotes: 4

Adrian
Adrian

Reputation: 46433

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.

Upvotes: 2

Related Questions