例行检查:选择是否真的选择了随机情况?
Context: https://tour.golang.org/concurrency/5
Hello everyone, I am learning Go following the above link.
The description says "It chooses one at random if multiple are ready." However, after making the main routine waiting for 2 second, before calling func fibonacci. The channels should be the following after 2 sec: c: 10 calls to get value from the channel quit: 0
It looks to me both channels are ready. If "It chooses one at random if multiple are ready" is true, then there is a 50% chance that the first call on the case in fibonacci will get the 0 from the quit channel. However, it is not the case. All 10 numbers will always get printed out before quitting. Hence it does not look like the selection is random. Am I missing something?
package main
import "fmt"
import "time"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
time.Sleep(2 * time.Second)
fibonacci(c, quit)
}
In addition, the next page: https://tour.golang.org/concurrency/6
It looks like the default code should print out either tick. or BOOM! at 500 milli second. However, only BOOM! is printed, always. If I changed the time in the default from 50 to 55, then both tick and BOOM get printed. Why is this? Does a After take precedence over a Tick in a select?
package main
import (
"fmt"
"time"
)
func main() {
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
return
default:
fmt.Println(" .")
time.Sleep(55 * time.Millisecond)
}
}
}
I think you're mistaken about what's happening in your main method... I'm gonna break down what I think is going on for clarity
func main() {
c := make(chan int)
quit := make(chan int) // make a couple channels
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c) // blocked here in goroutine
}
quit <- 0
}() // go func { ... }() - you're both writing this closure and invoking it as a goroutine
time.Sleep(2 * time.Second) // sleep for 2 seconds
fibonacci(c, quit) // call fibonacci passing in the channels
}
so what's actually happened here is you've called this closure as a goroutine then wait 2 seconds during which your goroutine is still sitting in the body of the for loop waiting to receive on c
, you call fibonacci
which executes as you expect going into the for-select, at which point you keep hitting that code on every iteration of the loop c <- x
(it receives, i gets incremented, you receive again, next value until the loop is over due to i == 10). then you proceed to the next line and send on the quit channel, the select executes that condition and your program exits.
As far as what executes first the language spec says;
Execution of a "select" statement proceeds in several steps:
1) For all the cases in the statement, the channel operands of receive operations and the channel and right-hand-side expressions of send statements are evaluated exactly once, in source order, upon entering the "select" statement. The result is a set of channels to receive from or send to, and the corresponding values to send. Any side effects in that evaluation will occur irrespective of which (if any) communication operation is selected to proceed. Expressions on the left-hand side of a RecvStmt with a short variable declaration or assignment are not yet evaluated. 2) If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection. Otherwise, if there is a default case, that case is chosen. If there is no default case, the "select" statement blocks until at least one of the communications can proceed. Unless the selected case is the default case, the respective communication operation is executed. 3) If the selected case is a RecvStmt with a short variable declaration or an assignment, the left-hand side expressions are evaluated and the received value (or values) are assigned. 4) The statement list of the selected case is executed.
It's only psuedo random in race conditions, problem is you're not creating a race condition.
You created an unbuffered chan
:
c := make(chan int)
This means that anything reading from the chan is going to block until something is written to it. And anything writing to the chan is going to block until something reads from it.
Thus in this code:
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
the <-c
blocks until something is put on c
. So it sits there waiting until after your time.Sleep()
completes, and gets to:
case c <- x:
upon which it alternates blocking back and forth reading one value, writing one value, until it reads 10 values, and then it sends 0
to quit
.
To create a buffered chan, you need to specify a size for the buffer.
c := make(chan int, 10)
But note, that even if you do this, it's still not going to behave as you expect, as you're reading all 10 values before you write to quit
. You need to put the writers in the same place, and the readers in the same place, not mixing them up.
In the first snippet of your code, quit
is never ready before the for
loop. And, in every iteration of the loop, c
is ready, and will block until a number is sent through. So there is really nothing select
can do but write to c
. It does not matter at all if you sleep two seconds.
The second piece of code is not bugged. The select
does randomly pick a case indeed. However, Go Playground has a fixed random generator, which means, on Playground, select will always pick one certain case in every run.