> Как с помощью контекста завершить работу горутины в 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- когда важен конкретный момент времени (например, до конца рабочего дня).
Пример кода
GOfunc worker(ctx context.Context, id int) {for {select {case <-ctx.Done():fmt.Printf("worker %d: stopping, reason: %v\n", id, ctx.Err())returndefault:// имитация полезной работы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")}
Более реалистичный пример с отменой по сигналу:
GOfunc main() {ctx, cancel := context.WithCancel(context.Background())defer cancel()go func() {sig := make(chan os.Signal, 1)signal.Notify(sig, os.Interrupt)<-sigcancel() // по 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()после отмены - полезно для диагностики причины завершения.
> Похожие задачи по Go
Что такое атомики и почему они быстрее мьютексов в Go
Какие инструменты используются для деплоя Go-приложения в продакшн
Как принимаются решения о внедрении изменений
Что лучше: выбрать из базы одним запросом тысячу id или сделать тысячу запросов по одному id
> Похожие задачи по backend
Что такое атомики и почему они быстрее мьютексов в Go
Какие инструменты используются для деплоя Go-приложения в продакшн
Как принимаются решения о внедрении изменений
Что лучше: выбрать из базы одним запросом тысячу id или сделать тысячу запросов по одному id
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью