Sobes.tech
Middle

Как са структурирани контекстите в езика Go?

sobes.tech AI

Отговор от AI

Контекстът (context.Context) в Go се използва за пренасяне на информация за крайни срокове, отменяния и други данни между границите на API. Той представлява дърво: всеки контекст е дъщеря на друг, освен кореновия (context.Background() или context.TODO()).

Основните методи на интерфейса context.Context:

  • Deadline() (deadline time.Time, ok bool): Връща крайния срок на контекста.
  • Done() <-chan struct{}: Връща канал, който се затваря при отменяне на контекста.
  • Err() error: Връща грешката, която е довела до отменянето на контекста.
  • Value(key any) any: Връща стойността, свързана с ключа в контекста.

Създават се с помощта на функциите на пакета context:

  • context.Background(): Кореновият празен контекст, използва се като основа за главния процес.
  • context.TODO(): Използва се като временна заместител, когато не е известно кой контекст да се използва.
  • context.WithCancel(parent Context): Създава дъщерен контекст и функция за отменяне.
  • context.WithDeadline(parent Context, d time.Time): Създава дъщерен контекст с крайна дата.
  • context.WithTimeout(parent Context, timeout time.Duration): Създава дъщерен контекст с таймаут.
  • context.WithValue(parent Context, key, val any): Създава дъщерен контекст с стойност.

Основните реализации на Context:

  • emptyCtx: Основен, неотменяем и без стойности. Използва се Background и TODO.
  • cancelCtx: Поддържа отменяне. Състои се от базов контекст и канал за отменяне.
  • timerCtx: Поддържа крайна дата/таймер. Съдържа cancelCtx и таймер.
  • valueCtx: Поддържа пренасяне на стойности. Съдържа базов контекст и ключ-стойност.

При отменяне на родителския контекст автоматично се отменят всички дъщерни. Това позволява разпространение на сигнали за отменяне или крайни дати по веригата на извиквания.

Пример за използване на context.WithCancel:

package main

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

func main() {
	// Създаване на коренов контекст и функция за отменяне
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel() // Гарантира отмена при изход от main

	go func() {
		select {
		case <-ctx.Done(): // Изчакване за отменяне на контекста
			fmt.Println("Задачата е отменена:", ctx.Err())
			return
		case <-time.After(5 * time.Second): // Или приключва след 5 секунди
			fmt.Println("Задачата е изпълнена")
		}
	}()

	// Отмяна на контекста от друго място
	time.Sleep(1 * time.Second)
	fmt.Println("Отмяна на контекста...")
	cancel()

	// Даваме време на горутината да обработи отменянето
	time.Sleep(100 * time.Millisecond)
}

Пример за използване на 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("Обработка на заявка за User ID:", userID)
	} else {
		fmt.Println("User ID не е намерен в контекста")
	}
}

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

Контекстите са безопасни за нишки и неизменяеми, което е важно за конкурентния код. Те не са предназначени за пренасяне на необработени параметри, а по-скоро за метаинформация, която влияе върху изпълнението на процеса.