context.WithCancel în Go

Creează un context copil și o funcție cancel; apelul cancel() anunță toate goroutine-urile care îl folosesc să se oprească.

Sintaxă
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

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 cancel cu ctx, _ := context.WithCancel(...). go vet raportează pierderea și resursele rămân ocupate; scrie defer 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.