Go - select

Syntax

The select statement waits for multiple send or receive operations simultaneously.

// Blocks until there's data available on ch1 or ch2
select {
case <-ch1:
        fmt.Println("Received from ch1")
case <-ch2:
        fmt.Println("Received from ch2")
}
  • The statement blocks as a whole until one of the operations becomes unblocked.
  • If several cases can proceed, a single one of them will be chosen at random.

Send and receive operations on a nil channel block forever. This can be used to disable a channel in a select statement:

ch1 = nil // Disables this channel
select {
case <-ch1:
        fmt.Println("Received from ch1") // Will not happen
case <-ch2:
        fmt.Println("Received from ch2")
}

Example - Wrong way of concurrency

Consider the methods:

func Every500ms(c1 chan string) {
  c1 <- "Every 500ms"
  time.Sleep(time.Millisecond * 500)
}

func Every2Seconds(c2 chan string) {
  c2 <- "Every 2 Seconds"
  time.Sleep(time.Second * 2)
}

Main:

func main() {
  c1 := make(chan string)
  c2 := make(chan string)

  go func() {
    for {
      go Every500ms(c1)
    }
  }()

  go func() {
    for {
      go Every2Seconds(c2)
    }
  }()

  // This will print 1 after another.. NEVER EVER DO THIS!
  for {
    fmt.Println(<-c1)
    fmt.Println(<-c2)
  }
}

Output:

Every 500ms
Every 2 Seconds
Every 500ms
Every 2 Seconds
Every 500ms
Every 2 Seconds
...

Example - Correct way for concurrency using select

Main:

// Concurrency allows parallelism
func main() {
  c1 := make(chan string)
  c2 := make(chan string)

  go func() {
    for {
      go Every500ms(c1)
    }
  }()

  go func() {
    for {
      go Every2Seconds(c2)
    }
  }()

  // Correct approach
  for {
    select {
    case msg1 := <-c1:
      fmt.Println(msg1)
    case msg2 := <-c2:
      fmt.Println(msg2)
    }
  }
}

Output:

Every 2 Seconds
Every 2 Seconds
Every 2 Seconds
Every 500ms
Every 2 Seconds
Every 500ms
Every 2 Seconds
Every 500ms
Every 500ms
Every 2 Seconds

Default case

The default case is always able to proceed and runs if all other cases are blocked.

// Never blocks
select {
case x := <-ch:
        fmt.Println("Received", x, "from ch")
default:
        fmt.Println("Nothing available on ch")
}

Example - Default case

Lets play with the time channels. time.Tick and time.After return channels.

func main() {
  // tick and After return time channels
  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(50 * time.Millisecond)
    }
  }
}

Output:

    .
    .
    .
tick.
    .
    .
tick.
    .
tick.
    .
    .
tick.
    .
    .
BOOM!

Handling timeouts

The function time.After is part of the standard library; it waits for a specified time to elapse and then sends the current time on the returned channel.

Example - Handling timeout

func main() {
  response := make(chan *http.Response, 1)
  errors := make(chan *error)

  go func() {
    resp, err := http.Get("http://abc.alpha.beta.gama.com")
    if err != nil {
      errors <- &err
    }
    response <- resp
  }()
  for {
    select {
    case r := <-response:
      fmt.Printf("%s", r.Body)
      return
    case err := <-errors:
      log.Fatal(*err)
    case <-time.After(200 * time.Millisecond):
      fmt.Printf("Timed out!")
      return
    }
  }
}

Block forever

// Block forever
select {}

A select statement blocks until at least one of it’s cases can proceed. With zero cases this will never happen.

http://www.golangbootcamp.com/book/concurrency


See also