Sobes.tech
Middle

Jak są zbudowane konteksty w języku Go?

sobes.tech AI

Odpowiedź od AI

Kontekst (context.Context) w Go jest używany do przekazywania informacji o terminach, anulowaniach i innych danych między granicami API. Reprezentuje drzewo: każdy kontekst jest potomkiem innego, z wyjątkiem korzenia (context.Background() lub context.TODO()).

Główne metody interfejsu context.Context:

  • Deadline() (deadline time.Time, ok bool): Zwraca termin kontekstu.
  • Done() <-chan struct{}: Zwraca kanał, który zamyka się przy anulowaniu kontekstu.
  • Err() error: Zwraca błąd, który spowodował anulowanie kontekstu.
  • Value(key any) any: Zwraca wartość powiązaną z kluczem w kontekście.

Tworzone są za pomocą funkcji pakietu context:

  • context.Background(): Pusty kontekst główny, używany jako podstawa głównego procesu.
  • context.TODO(): Używany jako zastępczy, gdy nie wiadomo, który kontekst użyć.
  • context.WithCancel(parent Context): Tworzy kontekst potomny i funkcję anulowania.
  • context.WithDeadline(parent Context, d time.Time): Tworzy kontekst potomny z terminem.
  • context.WithTimeout(parent Context, timeout time.Duration): Tworzy kontekst potomny z limitem czasu.
  • context.WithValue(parent Context, key, val any): Tworzy kontekst potomny z wartością.

Główne implementacje Context:

  • emptyCtx: Podstawowy, nie można go anulować i nie zawiera wartości. Używa Background i TODO.
  • cancelCtx: Obsługuje anulowanie. Składa się z bazowego kontekstu i kanału anulowania.
  • timerCtx: Obsługuje termin/limit czasu. Zawiera cancelCtx i timer.
  • valueCtx: Obsługuje przekazywanie wartości. Zawiera bazowy kontekst i pary klucz-wartość.

Przy anulowaniu kontekstu nadrzędnego, wszystkie jego potomne są automatycznie anulowane. Pozwala to na propagację sygnałów anulowania lub limitów czasowych w łańcuchu wywołań.

Przykład użycia context.WithCancel:

package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	// Tworzymy główny kontekst i funkcję anulowania
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel() // Zapewnia anulowanie przy wyjściu z main

	go func() {
		select {
		case <-ctx.Done(): // Czekamy na anulowanie kontekstu
			fmt.Println("Zadanie anulowane:", ctx.Err())
			return
		case <-time.After(5 * time.Second): // Lub kończymy po 5 sekundach
			fmt.Println("Zadanie zakończone")
		}
	}()

	// Anulujemy kontekst z innego miejsca
	time.Sleep(1 * time.Second)
	fmt.Println("Anulowanie kontekstu...")
	cancel()

	// Dajemy czas goroutine na obsłużenie anulowania
	time.Sleep(100 * time.Millisecond)
}

Przykład użycia context.WithValue:

package main

import (
	"context"
	"fmt"
)

type userIDKey string

func processRequest(ctx context.Context) {
	if userID, ok := ctx.Value(userIDKey("UserID")).(string); ok {
		fmt.Println("Przetwarzanie żądania dla User ID:", userID)
	} else {
		fmt.Println("Nie znaleziono User ID w kontekście")
	}
}

func main() {
	ctx := context.WithValue(context.Background(), userIDKey("UserID"), "12345")
	processRequest(ctx)
}

Konteksty są bezpieczne dla wątków i niezmiennicze, co jest ważne dla kodu współbieżnego. Nie są przeznaczone do przekazywania surowych parametrów funkcji, lecz raczej do metadanych wpływających na wykonanie procesu.