Перейти к основному содержимому
Модуль 7 · Урок 3 из 533/50 в курсе~12 min
Уроки модуля (3/5)

`select`: мультиплексирование каналов

select ожидает операций нескольких каналов одновременно. Это switch параллелизма Go: он позволяет вам реагировать на первое канал готов среди многих.

Синтаксис

Go
select {
case v := <-ch1:
    fmt.Println("ch1:", v)
case v := <-ch2:
    fmt.Println("ch2:", v)
case ch3 <- 99:
    fmt.Println("inviato su ch3")
case <-time.After(time.Second):
    fmt.Println("timeout")
default:
    fmt.Println("nessuno è pronto")
}

Семантика:

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

Шаблон тайм-аута

Go
select {
case res := <-fetch(url):
    use(res)
case <-time.After(2 * time.Second):
    log.Println("timeout: fetch troppo lento")
}

time.After(d) возвращает <-chan Time, который создает значение после d. Это классический способ ограничить ожидание.

Неблокирующий выбор по умолчанию

Go
select {
case msg := <-ch:
    process(msg)
default:
    // niente da fare, prosegui
}

Он «опрашивает» канал без блокировки. Полезно в путях кода, которые должен оставаться отзывчивым без обязательного ожидания.

Шаблон «Канал готов»

Объедините select с каналом отмены:

Go
func worker(in <-chan Job, done <-chan struct{}) {
    for {
        select {
        case j := <-in:
            process(j)
        case <-done:
            return       // chi gestisce done segnala lo shutdown
        }
    }
}

close(done) от координатора запускает case <-done в каждом ожидающая горутина: скоординированное выключение.

Цикл с выбором

Go
for {
    select {
    case v := <-input:
        if v == nil {
            return
        }
        handle(v)
    case <-time.After(5 * time.Second):
        keepAlive()
    case <-ctx.Done():
        return
    }
}

Типичный шаблон «цикла событий» для серверов и рабочих процессов.

Нулевой канал как «отключенный» случай

Сложный трюк: case <-ch с ch == nil никогда выбрано. Он позволяет динамически «отключать» регистр:

Go
var in chan int = source()
for {
    select {
    case v, ok := <-in:
        if !ok {
            in = nil    // disabilita questo case
            continue
        }
        handle(v)
    case <-ctx.Done():
        return
    }
}

Когда in закрывается, я обнуляю его: select продолжает обрабатывать только другие случаи.

Попробуйте это

Упражнение#go.m7.l3.e1
Попыток: 0Загрузка…

Реализуйте выбор с двумя случаями, которые получают данные из ch1 и ch2; выведите, какой из них прибыл первым.

Загрузка редактора…
Показать подсказку

Синтаксис `select {case ...: ... }` с одним регистром на канал.

Решение доступно после 3 попыток

Упражнение#go.m7.l3.e2
Попыток: 0Загрузка…

Добавьте случай тайм-аута в выбор, используя time.After(time.Second).

Загрузка редактора…
Показать подсказку

`time.After(d)` — это канал, который принимает данные после d.

Решение доступно после 3 попыток

Quiz#go.m7.l3.e3
Готово

Что делает выбор неблокирующим?

Go
select { case v := <-ch: ... ??? }
Варианты ответа

Резюме

  • select ожидает первого случая готовности среди нескольких операций канала.
  • Псевдослучайный выбор среди нескольких готовых дел.
  • default → неблокирующий выбор (опрос).
  • time.After(d) для таймаутов (обратите внимание на утечку в циклах: используйте context).
  • Шаблоны: канал выполнения, цикл событий, нулевой канал для отключения дела.
  • Пустой select{} = вечный блок: почти всегда ошибка.