Как в Go мультиплексировать чтение из нескольких каналов?

Ответ

Для одновременной работы с несколькими каналами в Go используется оператор select. Он блокирует выполнение горутины до тех пор, пока одна из операций чтения или записи в его case-ах не станет доступной. Если готовы несколько case-ов, select выбирает один из них случайным образом.

1. Ожидание первого готового канала

Это базовый сценарий, когда нужно дождаться данных из любого из нескольких источников.

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

go func() { 
    time.Sleep(1 * time.Second)
    ch1 <- "сообщение из канала 1"
}()
go func() { 
    time.Sleep(2 * time.Second)
    ch2 <- "сообщение из канала 2"
}()

// Ждем первое пришедшее сообщение
select {
case msg1 := <-ch1:
    fmt.Println("Получено:", msg1)
case msg2 := <-ch2:
    fmt.Println("Получено:", msg2)
}

2. Непрерывная обработка в цикле

Часто select используется внутри бесконечного цикла for для постоянной обработки входящих данных, например, в воркерах.

// Эта горутина будет обрабатывать сообщения из обоих каналов по мере их поступления
for {
    select {
    case msg1 := <-ch1:
        fmt.Println("Обработка сообщения из ch1:", msg1)
    case msg2 := <-ch2:
        fmt.Println("Обработка сообщения из ch2:", msg2)
    // Чтобы цикл не был вечным, нужна точка выхода
    // Например, через контекст или отдельный канал `done`
    }
}

3. Неблокирующее чтение с default

Если нужно попытаться прочитать из каналов, но не блокировать выполнение, если данных нет, используется default.

select {
case msg := <-ch1:
    fmt.Println("Получено сообщение:", msg)
default:
    // Этот блок выполнится немедленно, если в ch1 нет данных
    fmt.Println("Сообщений нет, идем дальше.")
}

4. Выход из цикла for-select

Важно предусмотреть механизм завершения цикла. Обычно это делается с помощью context или специального канала done.

ctx, cancel := context.WithCancel(context.Background())

// ... где-то в другом месте вызывается cancel() для завершения

for {
    select {
    case <-ctx.Done(): // Если контекст отменен
        fmt.Println("Получен сигнал завершения, выходим.")
        return // Выходим из функции и горутины
    case msg := <-ch1:
        fmt.Println("Обработка:", msg)
    }
}