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. rangepe 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.