1

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)
        }
    }
}
Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Pei Wang
  • 31
  • 1
  • 4
  • 2
    Why do you think both channels are ready? `quit` isn't ready as no goroutine is writing to it. – tkausl Jun 21 '18 at 01:35
  • I believe it is ready because the main routine has waited 2 seconds, edit: which should be more than enough for then go func routine to finish... right? – Pei Wang Jun 21 '18 at 01:46
  • No, the goroutine can't finish as it's waiting for someone to write values to `c`. – tkausl Jun 21 '18 at 01:52
  • See related / possible duplicate: [golang: How the select worked when multiple channel involved?](https://stackoverflow.com/questions/47645808/golang-how-the-select-worked-when-multiple-channel-involved/47648910#47648910) – icza Jun 21 '18 at 05:06

3 Answers3

2

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.

evanmcdonnal
  • 46,131
  • 16
  • 104
  • 115
  • I pick this as the answer for the great clarify. Thank you! Also special thanks goes to user "leaf bebop" for pointing out the quirk of GO playground for the second piece. – Pei Wang Jun 21 '18 at 02:11
  • @PeiWang yeah np, i was trying to make an example to simulate what you're trying to do but it's actually pretty difficult to do, might wanna try working off something like this; https://play.golang.org/p/efXAPo3iyyf the problem is the events actually have to happen at the same time. Even if you use buffered channels there like in the other variation it doesn't consider the events to happen at the same time as far as i can tell. so you actually have to have two independent parallel goroutines happen to send at the exact same time or something along those lines which can't be recreated easily – evanmcdonnal Jun 21 '18 at 02:18
1

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.

leaf bebop
  • 7,643
  • 2
  • 17
  • 27
0

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.

phemmer
  • 6,882
  • 3
  • 33
  • 31
  • 1
    the channel being buffered makes no difference at all, his goroutine still is stuck in a loop that reads off `c` repeatedly before the line of code in mains goroutine which sends on quit can run. – evanmcdonnal Jun 21 '18 at 01:58
  • Yeah, I was addressing that as you commented. It's actually both issues. With unbuffered chans, you would need 3 goroutines. One goroutine to make `c` ready (read from it), one goroutine to make `quit` ready (write to it), and one goroutine to do the `select`. With only 2 goroutines, you can't do all three, and so only one chan will be ready. – phemmer Jun 21 '18 at 02:08
  • nah, it has nothing to due with buffering, the runtime still has a notion of ordering beyond that. Check out this example program i just wrote to test this a bit; https://play.golang.org/p/9elo6PtxaGe - when my sleep is 100ms it produces heads every time, if i make it 1000ms it produces tails every time. if that's considered 'pseudo-random' then go devs need to address a bug in their implementation – evanmcdonnal Jun 21 '18 at 02:09
  • 1
    That is another matter entirely. That doesn't disprove the buffered vs unbuffered thing. The golang spec just means that when doing a `select` on multiple ready chans, you can't rely on any particular one being the one that will evaluate. – phemmer Jun 21 '18 at 02:12
  • Yes, and when you use buffered channels and wait a sufficient period of time (1 second, easily enough time for the goroutine with my sends to complete) the select reads from the same channel every time, meaning it's not random at all or just pre-loading the messages in the buffered channels doesn't mean go runtime decides those happened at the same time – evanmcdonnal Jun 21 '18 at 02:16