select în Go

Așteaptă mai multe operații pe canale deodată și o execută pe prima care este gata; cu default nu blochează.

Sintaxă
select {
case v := <-c1:
    ...
case c2 <- x:
    ...
case <-time.After(time.Second):
    // timeout
default:
    // nimic pregătit: nu așteaptă
}

Ce face select

select seamănă cu un switch, dar fiecare case este o trimitere sau o primire pe un canal. select așteaptă până când una dintre operații poate avea loc și o execută doar pe aceea. Dacă mai multe sunt gata în același timp, alege una la întâmplare.

Cu o ramură default, select nu mai așteaptă: dacă niciun canal nu este gata, execută default imediat (trimitere sau primire „fără blocare”). Cu time.After sau ctx.Done() obții timeout și anulare. Un select {} gol blochează pentru totdeauna.

Un canal nil nu este niciodată gata, deci o ramură cu un canal setat la nil este practic dezactivată: așa se oprește ascultarea unui canal deja închis.

Exemple

Exemplul 1: Timeout cu time.After

Primul răspuns vine după 5 ms, deci sosește; al doilea ar veni după 2 secunde, dar timeout-ul este de 300 ms.

package main

import (
    "fmt"
    "time"
)

func cerere(durata time.Duration) <-chan string {
    c := make(chan string, 1)
    go func() {
        time.Sleep(durata)
        c <- fmt.Sprintf("răspuns după %v", durata)
    }()
    return c
}

func main() {
    for _, d := range []time.Duration{5 * time.Millisecond, 2 * time.Second} {
        select {
        case r := <-cerere(d):
            fmt.Println(r)
        case <-time.After(300 * time.Millisecond):
            fmt.Println("timeout: serverul nu a răspuns în 300ms")
        }
    }
}

Rezultat

răspuns după 5ms
timeout: serverul nu a răspuns în 300ms

Exemplul 2: Trimitere fără blocare cu default

Coada are loc pentru 2 mesaje; al treilea este respins în loc să blocheze programul.

package main

import "fmt"

func main() {
    notificari := make(chan string, 2)
    for _, m := range []string{"mesaj nou", "comandă livrată", "reducere 20%"} {
        select {
        case notificari <- m:
            fmt.Println("pus în coadă:", m)
        default:
            fmt.Println("coada e plină, ignor:", m)
        }
    }

    select {
    case m := <-notificari:
        fmt.Println("primul din coadă:", m)
    default:
        fmt.Println("nimic de citit")
    }
}

Rezultat

pus în coadă: mesaj nou
pus în coadă: comandă livrată
coada e plină, ignor: reducere 20%
primul din coadă: mesaj nou

Exemplul 3: Citire din două canale până se închid amândouă

Canalul închis este setat la nil, ca select să nu-l mai aleagă. Totalurile nu depind de ordinea sosirii.

package main

import "fmt"

func trimite(valori ...int) <-chan int {
    c := make(chan int)
    go func() {
        defer close(c)
        for _, v := range valori {
            c <- v
        }
    }()
    return c
}

func main() {
    iasi := trimite(120, 80, 45)
    cluj := trimite(200, 15)
    totalIasi, totalCluj := 0, 0

    for iasi != nil || cluj != nil {
        select {
        case v, ok := <-iasi:
            if !ok {
                iasi = nil
                continue
            }
            totalIasi += v
        case v, ok := <-cluj:
            if !ok {
                cluj = nil
                continue
            }
            totalCluj += v
        }
    }
    fmt.Println("vânzări Iași:", totalIasi, "lei")
    fmt.Println("vânzări Cluj:", totalCluj, "lei")
}

Rezultat

vânzări Iași: 245 lei
vânzări Cluj: 215 lei

Greșeli frecvente

  • Așteptarea ca select să aleagă primul case din cod când mai multe sunt gata: alegerea este aleatorie.
  • default într-o buclă for { select { ... default: } } fără pauză: bucla consumă 100% CPU.
  • Citirea în continuare dintr-un canal închis: primești la nesfârșit valoarea zero. Verifică ok și setează canalul la nil.
  • time.After creat la fiecare iterație într-o buclă rapidă: creează multe timere; pentru bucle lungi folosește un time.Timer sau un context.

Comenzi înrudite

  • chan și canale (channels): Canalele (chan) transmit valori între goroutine în siguranță și le sincronizează; se creează cu make.
  • go și goroutine: Cuvântul go pornește o funcție într-o goroutine: un fir de execuție ușor, care rulează în paralel cu restul programului.
  • switch, case și default: Alege una din mai multe ramuri după valoarea unei expresii sau după prima condiție adevărată.
  • close: Închide un canal: anunță că nu se mai trimit valori; range se oprește, iar primirea dă ok == false.