> Как с помощью контекста завершить работу горутины в Go (Go)

Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы

Компании: VK

Стек: Go

> Пример ответа

Короткий ответ

Контекст в Go - это стандартный механизм отмены и дедлайнов, который передаётся первым аргументом в функции. Для завершения горутины через контекст нужно: создать контекст с context.WithCancel, context.WithTimeout или context.WithDeadline, передать его в горутину, и в горутине слушать канал ctx.Done(). При вызове cancel() или истечении таймаута канал закрывается, и горутина получает сигнал завершиться. Это идиоматичный способ управлять жизненным циклом конкурентных операций.

Подробное объяснение

Контекст решает проблему graceful shutdown горутин, которые выполняют длительные операции (сетевые запросы, чтение из каналов, обработку данных). Без контекста горутина может заблокироваться навсегда, если операция не завершится сама.

Основные компоненты:

  • context.Context - интерфейс с методами Deadline(), Done(), Err(), Value().
  • context.WithCancel(parent) - возвращает контекст и функцию cancel(), вызов которой закрывает Done().
  • context.WithTimeout(parent, duration) - автоматически отменяет контекст через указанное время.
  • context.WithDeadline(parent, time) - отменяет в конкретный момент времени.

Механика: когда вызывается cancel() или истекает дедлайн, канал ctx.Done() закрывается. Горутина должна проверять этот канал в select или через ctx.Err(). Важно, что отмена контекста не убивает горутину принудительно - она лишь сигнализирует, а горутина сама решает, как завершиться (освободить ресурсы, закрыть соединения, вернуть частичный результат).

Контекст также распространяется вглубь - если вы передаёте контекст в функцию, которая запускает дочерние горутины, они должны получить тот же контекст (или его производный), чтобы вся цепочка завершилась согласованно.

На практике

В реальном коде контекст используется для:

  • отмены HTTP-запросов при отключении клиента;
  • остановки worker pool при завершении программы;
  • ограничения времени выполнения запроса к БД или внешнему API;
  • передачи метаданных (trace ID, auth token) через context.WithValue.

Паттерн передачи: контекст всегда первым параметром функции, никогда не хранится в структуре (исключение - библиотеки, где это явно документировано). При создании производного контекста обязательно вызывать cancel() через defer, чтобы избежать утечки ресурсов.

При выборе способа отмены:

  • WithCancel - когда нужно явное управление извне;
  • WithTimeout - когда есть жёсткий лимит времени;
  • WithDeadline - когда важен конкретный момент времени (например, до конца рабочего дня).

Пример кода

GO
func worker(ctx context.Context, id int) {
for {
select {
case <-ctx.Done():
fmt.Printf("worker %d: stopping, reason: %v\n", id, ctx.Err())
return
default:
// имитация полезной работы
time.Sleep(100 * time.Millisecond)
fmt.Printf("worker %d: working\n", id)
}
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
go worker(ctx, 1)
go worker(ctx, 2)
time.Sleep(3 * time.Second)
fmt.Println("main: done")
}

Более реалистичный пример с отменой по сигналу:

GO
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt)
<-sig
cancel() // по Ctrl+C завершаем все горутины
}()
for i := 0; i < 5; i++ {
go worker(ctx, i)
}
<-ctx.Done()
fmt.Println("main: all workers stopped")
}

Как отвечать на собеседовании

Начните с определения: контекст - это механизм отмены и дедлайнов. Затем объясните ключевую идею: горутина сама проверяет ctx.Done() и завершается, а не принудительно убивается. Приведите пример с WithCancel и WithTimeout. Упомяните, что контекст передаётся первым аргументом, и что cancel() нужно вызывать через defer. Если спросят про context.WithValue - скажите, что это для метаданных, а не для отмены. Хорошо показать понимание, что отмена распространяется на дочерние контексты.

Что проверяет интервьюер

  • Понимание принципа cooperative cancellation (кооперативная отмена) - горутина не убивается, а уведомляется.
  • Умение работать с select и каналом Done().
  • Знание разницы между WithCancel, WithTimeout, WithDeadline.
  • Понимание правил передачи контекста (первый аргумент, не хранить в структуре).
  • Осознание важности вызова cancel() для предотвращения утечек.
  • Способность объяснить, как контекст помогает при graceful shutdown.

Типичные ошибки

  • Ожидание, что отмена контекста принудительно остановит горутину - это не так, горутина должна сама проверить Done().
  • Забыть вызвать cancel() - утечка ресурсов, контекст не будет освобождён.
  • Передавать context.Background() в функции, которые должны быть отменяемыми.
  • Хранить контекст в структуре - нарушение идиомы, контекст должен передаваться явно.
  • Использовать context.WithValue для передачи обязательных параметров вместо обычных аргументов.
  • Не использовать select с ctx.Done() в цикле - горутина может заблокироваться навсегда.
  • Игнорировать ctx.Err() после отмены - полезно для диагностики причины завершения.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью