Гарантируется ли порядок выполнения case-блоков в операторе select?

Ответ

Нет, не гарантируется.

Если несколько каналов в select готовы к выполнению одновременно (т.е. можно прочитать или записать данные), Go выбирает один из них псевдослучайным образом. Это не FIFO (First-In, First-Out) и не LIFO (Last-In, First-Out).

Такое поведение введено намеренно для обеспечения справедливости (fairness) и предотвращения голодания (starvation), когда один из каналов мог бы постоянно перехватывать все операции, если бы у него был более высокий приоритет.

Пример:

package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        ch1 <- "from ch1"
    }()

    go func() {
        ch2 <- "from ch2"
    }()

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

    // При многократном запуске вывод будет меняться
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println("Received", msg1)
        case msg2 := <-ch2:
            fmt.Println("Received", msg2)
        }
    }
}

Как задать приоритет?

Если вам нужен приоритетный выбор, вы можете использовать select с блоком default для неблокирующей проверки более приоритетного канала. Если он не готов, можно проверять остальные.

// Пытаемся прочитать из priorityCh без блокировки
select {
case msg := <-priorityCh:
    fmt.Println("High priority message:", msg)
default:
    // priorityCh не готов, проверяем другие каналы
    select {
    case msg := <-priorityCh:
        fmt.Println("High priority message:", msg)
    case msg := <-normalCh:
        fmt.Println("Normal priority message:", msg)
    case <-time.After(100 * time.Millisecond):
        fmt.Println("No messages received.")
    }
}