Как оператор `select` выбирает готовый к выполнению `case`?

Ответ

Оператор select в Go позволяет горутине ждать нескольких операций связи. Его поведение при выборе case следующее:

  1. Если готов только один case: Выполняется именно он.
  2. Если готовы несколько case: Выбирается один из них псевдослучайным образом. Это ключевая особенность, которая обеспечивает справедливость (fairness) и предотвращает ситуацию, когда один канал постоянно "перехватывает" все операции, приводя к голоданию (starvation) других.
  3. Если ни один case не готов:
    • Если есть ветка default, выполняется она.
    • Если ветки default нет, select блокируется до тех пор, пока один из case не станет готов.

Пример:

ch1 := make(chan string)
ch2 := make(chan string)

go func() { ch1 <- "from one" }()
go func() { ch2 <- "from two" }()

// Даем горутинам время на запуск
time.Sleep(10 * time.Millisecond)

select {
case msg1 := <-ch1:
    fmt.Println(msg1)
case msg2 := <-ch2:
    fmt.Println(msg2)
}

В этом примере, если обе горутины успеют отправить данные в свои каналы, select выберет один из case случайным образом. При многократном запуске результат будет разным.