Reputation: 11822
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?
Upvotes: 8
Views: 3125
Reputation: 79566
Your first case isn't executing.
Here's what your program does:
0
through 4
on the channel, which all block, because there is nothing reading the channel, so fall through to the default.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.
Upvotes: 3
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
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
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