go routine: does select really pick a random case?

Issue

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

Solution

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.

Answered By – evanmcdonnal

Answer Checked By – Mildred Charles (GoLangFix Admin)

Leave a Reply

Your email address will not be published.