3

Following on from this answer, if a goroutine is selecting on two channels, is it guaranteed that the channels are selected in the same order that they are sent on? I'm particularly interested in the case where the sender is single threaded.

As an example, is it guaranteed that this code always produces the same output:

package main

import (
  "fmt"
  "time"
)

var x, y chan int

func worker() {
  for {
    select {
    case v := <- x:
      fmt.Println(v)
    case v := <- y:
      fmt.Println(v)
    }
  }
}

func main() {
  x = make(chan int)
  y = make(chan int)
  go worker()
  x <- 1
  y <- 2
  <- time.After(1 * time.Second)
}

My experiments would seem to indicate that this is guaranteed for unbuffered channels, as shown, but not guaranteed for buffered channels. Can someone confirm that please?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Tom
  • 7,269
  • 1
  • 42
  • 69
  • 4
    The linked question has literally nothing to do with your question. Receiving from 1 channel is receive first what is "sent first". Select from two channels is pseudorandom according to the spec. – Volker Aug 04 '21 at 11:50
  • Actually, the linked answer explains the mechanism responsible for the deterministic outcome of my example code quite well. – Tom Aug 04 '21 at 13:56

2 Answers2

9

No. It's not guaranteed. In fact, it's randomized. From the Go language spec (emphasis added):

  1. 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.
Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
7

Although in your example it's guaranteed that you'll see 1 printed first, but not because of how select works.

It's because x is an unbuffered channel, and send on it will block until someone receives from it, and only then can you send on channel y.

So your select inside worker() won't see 2 ready channels at the same time.

It if would, then one is chosen pseudo-randomly. For details, see How does select work when multiple channels are involved?

So note that if you'd use buffered channels:

x = make(chan int, 1)
y = make(chan int, 1)

Then send on x and y wouldn't block, and now it's up to the goroutine scheduler how goroutines are executed. It's possible the main goroutine can send both values before the worker() goroutine reaches and evaluates the channel operations, and so you could see 1 then 2 printed, just as well as 2 and then 1.

icza
  • 389,944
  • 63
  • 907
  • 827