Ce face context.WithCancel
context.WithCancel(parinte) returnează un context nou și o funcție cancel. Când apelezi cancel(), canalul ctx.Done() se închide, ctx.Err() devine context.Canceled, iar toate contextele create din el sunt anulate și ele. Contextul este anulat și dacă părintele este anulat.
Este modul obișnuit de a opri un grup de goroutine: le dai tuturor același ctx, ele verifică ctx.Done(), iar la final (sau la prima eroare) apelezi cancel().
cancel trebuie apelat întotdeauna, chiar dacă operația s-a terminat normal, altfel resursele contextului rămân ocupate până la anularea părintelui. Modelul este ctx, cancel := context.WithCancel(...) urmat de defer cancel(); go vet avertizează dacă uiți. Apelurile repetate ale lui cancel nu fac nimic.
Parametri
| Parametru | Descriere |
|---|---|
parent |
Contextul părinte, de obicei context.Background() sau contextul primit de funcție. |
Ce returnează
Contextul copil și funcția cancel (context.CancelFunc, adică func()).
Exemple
Exemplul 1: Oprirea unor lucrători
Trei lucrători rulează până când main apelează cancel(). Rezultatele sunt adunate și afișate după Wait.
package main
import (
"context"
"fmt"
"sync"
"time"
)
func lucrator(ctx context.Context, id int, gata *[3]string) {
for {
select {
case <-ctx.Done():
gata[id] = fmt.Sprintf("lucrătorul %d s-a oprit (%v)", id+1, ctx.Err())
return
case <-time.After(10 * time.Millisecond):
// un pas de lucru
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var rezultate [3]string
var wg sync.WaitGroup
for i := range 3 {
wg.Go(func() { lucrator(ctx, i, &rezultate) })
}
time.Sleep(60 * time.Millisecond)
fmt.Println("Anulez...")
cancel()
wg.Wait()
for _, r := range rezultate {
fmt.Println(r)
}
}
Rezultat
Anulez...
lucrătorul 1 s-a oprit (context canceled)
lucrătorul 2 s-a oprit (context canceled)
lucrătorul 3 s-a oprit (context canceled)
Exemplul 2: Prima găsire oprește căutarea
Căutăm un produs în mai multe depozite. Primul care îl găsește anunță rezultatul, iar cancel() îi oprește pe ceilalți.
package main
import (
"context"
"fmt"
"time"
)
func cauta(ctx context.Context, depozit string, durata time.Duration, rezultat chan<- string) {
select {
case <-time.After(durata):
select {
case rezultat <- depozit:
case <-ctx.Done():
}
case <-ctx.Done():
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rezultat := make(chan string)
go cauta(ctx, "Iași", 300*time.Millisecond, rezultat)
go cauta(ctx, "Cluj", 30*time.Millisecond, rezultat)
go cauta(ctx, "Brașov", 500*time.Millisecond, rezultat)
fmt.Println("Găsit în depozitul din", <-rezultat)
cancel() // ceilalți se opresc
fmt.Println("Stare context:", ctx.Err())
}
Rezultat
Găsit în depozitul din Cluj
Stare context: context canceled
Exemplul 3: Anularea părintelui anulează copiii
package main
import (
"context"
"fmt"
)
func main() {
parinte, anuleazaParinte := context.WithCancel(context.Background())
copil, anuleazaCopil := context.WithCancel(parinte)
defer anuleazaCopil()
fmt.Println("Înainte:", parinte.Err(), copil.Err())
anuleazaParinte()
<-copil.Done()
fmt.Println("După: ", parinte.Err(), "|", copil.Err())
}
Rezultat
Înainte: <nil> <nil>
După: context canceled | context canceled
Greșeli frecvente
- Ignori
cancelcuctx, _ := context.WithCancel(...).go vetraportează pierderea și resursele rămân ocupate; scriedefer cancel(). - Creezi contextul, dar goroutine-urile nu verifică niciodată
ctx.Done(). Anularea doar trimite un semnal; codul trebuie să-l asculte.
Comenzi înrudite
context.Context: Interfața care duce prin program semnalul de anulare, termenul limită și valori legate de o cerere (ctx).context.WithTimeout: Creează un context care se anulează singur după durata dată; limita de timp standard pentru cereri și interogări.context.WithCancelCause: Ca WithCancel, dar cancel primește motivul anulării; context.Cause(ctx) returnează acel motiv.sync.WaitGroup: Așteaptă ca un grup de goroutine să termine: Add mărește contorul, Done îl scade, Wait blochează până ajunge la zero.