Что такое context в Go? Какие бывают context в Go? Когда их нужно использовать и зачем?go-53

Что такое context?

context в Go — это специальный пакет, предоставляющий механизм для:

  • Передачи значений между функциями
  • Управления временем выполнения операций (таймауты)
  • Прерывания (отмены) длительных операций
  • Распространения сигналов через границы API

Основной интерфейс:

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}

Основные виды context

1. context.Background

Базовый контекст, используемый как корневой:

ctx := context.Background()
  • Никогда не отменяется
  • Не имеет deadline
  • Не содержит значений

2. context.TODO

Аналогичен Background(), но используется как временный заглушка:

ctx := context.TODO()
  • Для случаев, когда контекст нужен, но пока не ясно какой
  • Помогает статическому анализу кода

3. context.WithCancel

Создает контекст с возможностью отмены:

ctx, cancel := context.WithCancel(context.Background())
defer cancel() // Важно вызывать cancel для освобождения ресурсов
  • Возвращает функцию cancel(), при вызове которой контекст отменяется
  • Полезен для ручного прерывания операций

4. context.WithTimeout

Контекст с автоматической отменой по таймауту:

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
  • Отменяется после указанного времени
  • Особенно полезен для HTTP-запросов, DB-запросов

5. context.WithDeadline

Аналогичен WithTimeout, но принимает конкретное время:

ctx, cancel := context.WithDeadline(context.Background(), time.Date(2023, 12, 31, 23, 59, 59, 0, time.UTC))
defer cancel()

6. context.WithValue

Контекст для передачи значений:

ctx := context.WithValue(context.Background(), "requestID", "12345")
  • Не следует использовать для передачи обязательных параметров
  • Только для request-scoped данных (аутентификация, трейсинг и т.д.)

Когда использовать context?

1. HTTP-запросы

func handler(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    // Передаем контекст в нижележащие вызовы
    result, err := someLongOperation(ctx)
}

2. Database операции

ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()

row := db.QueryRowContext(ctx, "SELECT ...")

3. GRPC сервисы

GRPC построен вокруг context и автоматически передает его между вызовами.

4. Длительные фоновые задачи

func worker(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            return // завершаем работу при отмене
        default:
            // выполняем работу
        }
    }
}

Как правильно работать с context?

1. Передавайте context явно

Контекст должен быть первым параметром:

func DoSomething(ctx context.Context, arg1, arg2 string) error

2. Проверяйте контекст в циклах

for {
    select {
    case <-ctx.Done():
        return ctx.Err()
    default:
        // работа
    }
}

3. Не храните context в структурах

Контекст должен передаваться между функциями, а не храниться:

// Плохо:
type Server struct {
    ctx context.Context
}

// Хорошо:
func (s *Server) HandleRequest(ctx context.Context)

4. Используйте context для трейсинга

ctx = context.WithValue(ctx, "traceID", generateTraceID())

Распространенные ошибки

1. Игнорирование контекста

func BadPractice(arg string) { // Нет параметра ctx
    // Код, который может блокироваться
}

2. Неправильное использование WithValue

// Плохо - использование строк как ключей может привести к коллизиям
ctx := context.WithValue(ctx, "userID", 123)

// Хорошо - использовать свой тип
type ctxKey string
const userIDKey ctxKey = "userID"
ctx := context.WithValue(ctx, userIDKey, 123)

3. Утечка горутин из-за отсутствия отмены

go func() {
    // Горутина будет висеть, даже если контекст отменен
}()

Резюмируем

  1. Context — это мощный механизм для управления временем жизни операций
  2. Основные виды: Background, TODO, WithCancel, WithTimeout, WithDeadline, WithValue
  3. Контекст должен быть первым параметром в функциях
  4. Используйте для: HTTP-обработчиков, DB-запросов, GRPC, длительных операций
  5. Всегда проверяйте ctx.Done() в долгих операциях
  6. Не храните context в структурах
  7. WithValue используйте только для request-scoped данных
  8. Всегда вызывайте cancel() для WithCancel/WithTimeout/WithDeadline

Правильное использование context делает Go-программы более надежными и предсказуемыми.