chan și canale (channels) în Go

Canalele (chan) transmit valori între goroutine în siguranță și le sincronizează; se creează cu make.

Sintaxă
c := make(chan int)       // fără buffer
c := make(chan int, 10)   // cu buffer de 10
c <- v          // trimite
v := <-c        // primește
v, ok := <-c    // ok = false după close
chan<- int      // doar trimitere
<-chan int      // doar primire

Ce face chan și canale (channels)

Un canal (chan T) este un „tub” prin care goroutinele își trimit valori de tip T. c <- v trimite, v := <-c primește. Trimiterea și primirea sunt sigure fără mutex, iar canalul sincronizează automat goroutinele. Se creează cu make; valoarea zero este nil, pe care nu se poate trimite sau primi niciodată.

Un canal fără buffer (make(chan int)) blochează expeditorul până când cineva primește valoarea: este un punct de întâlnire. Un canal cu buffer (make(chan int, 3)) acceptă până la 3 valori fără să aștepte, apoi blochează când este plin; primirea blochează când este gol. len(c) și cap(c) dau numărul de valori din buffer și capacitatea.

Expeditorul închide canalul cu close(c) ca să anunțe că nu mai vin valori: for v := range c se oprește, iar v, ok := <-c dă ok == false. În parametri, chan<- T permite doar trimiterea, iar <-chan T doar primirea; compilatorul verifică folosirea corectă.

Exemple

Exemplul 1: Canal cu buffer: coadă de comenzi

Buffer-ul de 3 primește valorile fără o goroutine care să aștepte; close oprește bucla range.

package main

import "fmt"

func main() {
    comenzi := make(chan string, 3)
    comenzi <- "cafea"
    comenzi <- "ceai"
    fmt.Println("în buffer:", len(comenzi), "din", cap(comenzi))
    comenzi <- "ciocolată caldă"
    close(comenzi)

    for c := range comenzi {
        fmt.Println("servesc:", c)
    }
    v, ok := <-comenzi
    fmt.Printf("după close: %q, ok = %v\n", v, ok)
}

Rezultat

în buffer: 2 din 3
servesc: cafea
servesc: ceai
servesc: ciocolată caldă
după close: "", ok = false

Exemplul 2: Pipeline: producător, procesator, consumator

Fiecare etapă rulează în propria goroutine; canalele păstrează ordinea valorilor, deci rezultatul este determinist.

package main

import "fmt"

func genereaza(numere ...int) <-chan int {
    iesire := make(chan int)
    go func() {
        defer close(iesire)
        for _, n := range numere {
            iesire <- n
        }
    }()
    return iesire
}

func patrate(intrare <-chan int) <-chan int {
    iesire := make(chan int)
    go func() {
        defer close(iesire)
        for n := range intrare {
            iesire <- n * n
        }
    }()
    return iesire
}

func main() {
    for p := range patrate(genereaza(2, 3, 4, 5)) {
        fmt.Println(p)
    }
}

Rezultat

4
9
16
25

Exemplul 3: Grup de lucrători (worker pool)

3 lucrători procesează 6 sarcini; rezultatele vin în orice ordine, așa că le punem în slice după numărul sarcinii și le afișăm la final.

package main

import (
    "fmt"
    "sync"
)

type sarcina struct{ id, valoare int }
type rezultat struct{ id, valoare int }

func lucrator(sarcini <-chan sarcina, rezultate chan<- rezultat, wg *sync.WaitGroup) {
    defer wg.Done()
    for s := range sarcini {
        rezultate <- rezultat{s.id, s.valoare * 10}
    }
}

func main() {
    valori := []int{4, 8, 15, 16, 23, 42}
    sarcini := make(chan sarcina, len(valori))
    rezultate := make(chan rezultat, len(valori))

    var wg sync.WaitGroup
    for range 3 {
        wg.Add(1)
        go lucrator(sarcini, rezultate, &wg)
    }
    for i, v := range valori {
        sarcini <- sarcina{i, v}
    }
    close(sarcini)

    wg.Wait()
    close(rezultate)

    finale := make([]int, len(valori))
    for r := range rezultate {
        finale[r.id] = r.valoare
    }
    fmt.Println(finale)
}

Rezultat

[40 80 150 160 230 420]

Greșeli frecvente

  • Trimitere pe un canal fără buffer fără nicio goroutine care să primească: fatal error: all goroutines are asleep - deadlock!.
  • Trimitere pe un canal închis: panic: send on closed channel. Doar expeditorul închide canalul, o singură dată.
  • Închiderea canalului de către destinatar sau de mai multe goroutine: panic: close of closed channel.
  • range pe un canal care nu se închide niciodată: bucla nu se termină.

Comenzi înrudite

  • 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.
  • select: Așteaptă mai multe operații pe canale deodată și o execută pe prima care este gata; cu default nu blochează.
  • close: Închide un canal: anunță că nu se mai trimit valori; range se oprește, iar primirea dă ok == false.
  • make: Creează și inițializează slice-uri, map-uri și canale, cu lungime, capacitate sau buffer dat.
  • range: Parcurge într-un for elementele unui slice, array, map, text, canal, număr sau iterator.