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-каналом | вимкнено (блокується назавжди) |