Ce face go și goroutine
go f(x) pornește apelul f(x) într-o goroutine nouă și continuă imediat, fără să aștepte rezultatul. O goroutine este un fir de execuție foarte ușor, gestionat de Go: un program poate rula mii sau chiar milioane deodată. main rulează și el într-o goroutine; când main se termină, programul se oprește, chiar dacă alte goroutine nu au terminat.
De aceea trebuie să aștepți goroutinele pornite. Cel mai simplu, cu sync.WaitGroup: Add(1) înainte de pornire, Done() la final (de obicei cu defer) și Wait() în main. Din Go 1.25, wg.Go(func() { ... }) face toate trei pașii. Rezultatele se trimit înapoi prin canale sau se scriu în poziții separate ale unui slice.
Ordinea în care rulează goroutinele nu este garantată. Pentru rezultate deterministe, colectează-le și afișează-le la final, în ordinea dorită. Datele modificate de mai multe goroutine trebuie protejate cu sync.Mutex, cu sync/atomic sau trimise prin canale; altfel apar erori de tip „data race” (detectate cu go run -race).
Exemple
Exemplul 1: Calcul în paralel cu WaitGroup, rezultate în ordine
Fiecare goroutine scrie în propria poziție din rezultate, deci nu e nevoie de mutex; afișarea se face după Wait, în ordine.
package main
import (
"fmt"
"sync"
)
func sumaPana(n int) int {
s := 0
for i := 1; i <= n; i++ {
s += i
}
return s
}
func main() {
limite := []int{10, 100, 1000, 10000}
rezultate := make([]int, len(limite))
var wg sync.WaitGroup
for i, n := range limite {
wg.Add(1)
go func() {
defer wg.Done()
rezultate[i] = sumaPana(n)
}()
}
wg.Wait() // așteptăm toate goroutinele
for i, n := range limite {
fmt.Printf("suma 1..%d = %d\n", n, rezultate[i])
}
}
Rezultat
suma 1..10 = 55
suma 1..100 = 5050
suma 1..1000 = 500500
suma 1..10000 = 50005000
Exemplul 2: Contor partajat protejat cu Mutex
100 de goroutine cresc același contor; Lock/Unlock evită pierderea actualizărilor. wg.Go (Go 1.25) face Add și Done automat.
package main
import (
"fmt"
"sync"
)
func main() {
var (
mu sync.Mutex
wg sync.WaitGroup
vizite int
)
for range 100 {
wg.Go(func() {
for range 10 {
mu.Lock()
vizite++
mu.Unlock()
}
})
}
wg.Wait()
fmt.Println("vizite totale:", vizite)
}
Rezultat
vizite totale: 1000
Exemplul 3: Rezultate trimise prin canal
Goroutinele termină în orice ordine; colectăm rezultatele în map, apoi le afișăm sortat.
package main
import (
"fmt"
"slices"
"strings"
)
type rezultat struct {
oras string
litere int
}
func main() {
orase := []string{"Iași", "Cluj-Napoca", "Arad", "Timișoara"}
canal := make(chan rezultat)
for _, o := range orase {
go func() {
canal <- rezultat{o, len([]rune(strings.ReplaceAll(o, "-", "")))}
}()
}
litere := map[string]int{}
for range orase {
r := <-canal
litere[r.oras] = r.litere
}
slices.Sort(orase)
for _, o := range orase {
fmt.Printf("%-12s %d litere\n", o, litere[o])
}
}
Rezultat
Arad 4 litere
Cluj-Napoca 10 litere
Iași 4 litere
Timișoara 9 litere
Greșeli frecvente
- Pornirea goroutinelor fără să le aștepți:
mainse termină, iar ele sunt oprite înainte de a face ceva. wg.Add(1)în interiorul goroutinei:Waitpoate trece înainte deAdd. ApeleazăAddînainte dego;go vetsemnaleazăWaitGroup.Add called from inside new goroutine.- Afișarea direct din goroutine când ordinea contează: ordinea diferă de la o rulare la alta.
- Copierea unui
sync.WaitGroupsausync.Mutexprin trimitere ca valoare:go vetdăpasses lock by value. Trimite un pointer.
Comenzi înrudite
chan și canale (channels): Canalele (chan) transmit valori între goroutine în siguranță și le sincronizează; se creează cu make.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.closures (funcții anonime): Funcții anonime care „țin minte” variabilele din jurul lor și le pot citi și modifica după ce funcția exterioară s-a terminat.panic și recover: panic oprește execuția normală pentru erori grave; recover, apelat într-un defer, poate opri panic-ul.