Перейти до змісту

select

Оператор select чекає на кілька операцій з каналами одночасно й продовжує з тією, яка готова першою. Це керівна структура, що робить канали композиційними — таймаути, скасування та мультиплексування будуються на ній.

select {
case v := <-ch1:
    fmt.Println("from ch1:", v)
case v := <-ch2:
    fmt.Println("from ch2:", v)
}

Кожен case — це надсилання чи отримання. select блокує, доки одна з них не зможе виконатися, виконує цей випадок і продовжує. Якщо кілька готові водночас, він обирає одну випадково — тож не покладайтеся на порядок випадків як на пріоритет.

c1 := make(chan string, 1)
c2 := make(chan string, 1)
c1 <- "hello"            // лише c1 має значення

select {
case m := <-c1:
    fmt.Println(m)       // output: hello
case m := <-c2:
    fmt.Println(m)
}

default: не блокувати

Випадок default виконується негайно, якщо жоден інший не готовий, перетворюючи select на неблокувальну операцію. Так опитують канал чи роблять неблокувальне надсилання.

ch := make(chan int)     // порожній, без відправника

select {
case v := <-ch:
    fmt.Println("got", v)
default:
    fmt.Println("nothing ready")   // output: nothing ready
}

Таймаути через time.After

time.After(d) повертає канал, що видає значення через тривалість d. Покладіть його в select — і отримаєте таймаут безкоштовно:

ch := make(chan int)     // ніхто не надішле

select {
case v := <-ch:
    fmt.Println("got", v)
case <-time.After(10 * time.Millisecond):
    fmt.Println("timeout")          // output: timeout
}

Цикл for-select із каналом done

Робоча конячка для довготривалої горутини: цикл на select, який обробляє роботу на одному каналі та сигнал зупинки на іншому. Закритий канал змушує кожне отримання повертатися негайно, тож він транслює «стоп» усім слухачам.

logs := make(chan string)
shutdown := make(chan struct{})
finished := make(chan struct{})

go func() {                          // фоновий логер
    for {
        select {
        case line := <-logs:
            fmt.Println("LOG:", line)
        case <-shutdown:
            fmt.Println("logger stopped")
            close(finished)
            return
        }
    }
}()

logs <- "server started"
logs <- "request handled"
close(shutdown)          // сказати логеру зупинитися
<-finished               // чекати, доки він справді вийде
// output:
// LOG: server started
// LOG: request handled
// logger stopped

Вимкнення випадку через nil-канал

Отримання (чи надсилання) на nil-каналі блокується назавжди, тож присвоєння змінній каналу nil прибирає його випадок із select. Це ідіоматичний спосіб припинити слухати канал посеред циклу, не перебудовуючи select.

var ch chan int          // nil
select {
case <-ch:               // ніколи не спрацює — ch є nil
    fmt.Println("unreachable")
case <-time.After(time.Millisecond):
    fmt.Println("only this can happen")   // output: only this can happen
}

Порожній select блокується назавжди

select {} без випадків блокує горутину назавжди — інколи вживають, щоб «припаркувати» main, доки працюють фонові горутини, але в main, коли більше нічого не може виконатися, це запускає детектор взаємних блокувань:

func main() {
    select {}   // без випадків — блокування назавжди
}
// fatal error: all goroutines are asleep - deadlock!

Швидка довідка

Форма Значення
select { case ...: } чекати, доки одна операція з каналом готова
кілька готові одну обрано випадково
default: виконати, якщо нічого не готове (неблокувально)
case <-time.After(d): гілка таймауту
for { select { ... } } довготривалий мультиплексований цикл
case <-done: сигнал зупинки (закритий канал транслює)
випадок із nil-каналом вимкнено (блокується назавжди)

Джерела