> Как использовать пакет context для передачи сигнала закрытия 15 горутинам в Go (Go)
Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы
Компании: ЦУМ
Стек: Go
> Пример ответа
Короткий ответ
Для передачи сигнала закрытия 15 горутинам используйте context.WithCancel - создайте один контекст, передайте его во все горутины, а при необходимости остановки вызовите cancel(). Каждая горутина должна слушать ctx.Done() в select или проверять его в цикле. Это стандартный паттерн graceful shutdown, который гарантирует, что все горутины получат сигнал одновременно и смогут корректно завершить работу.
Подробное объяснение
Пакет context предоставляет механизм распространения сигналов отмены через всю цепочку вызовов. Основная идея: создаётся родительский контекст с функцией cancel, которая при вызове закрывает канал ctx.Done(). Все горутины, получившие этот контекст (напрямую или через дочерние контексты), могут отслеживать закрытие канала.
Ключевые моменты:
context.WithCancel(parent)возвращаетctxиcancel. Вызовcancel()- идиоматичный способ сигнализировать об остановке.- Канал
ctx.Done()закрывается ровно один раз, и все горутины, слушающие его, получают событие одновременно. - Горутина должна явно проверять
ctx.Done()- контекст не прерывает выполнение принудительно, он лишь предоставляет сигнал. - Важно вызывать
cancel()черезdeferв функции, создающей контекст, чтобы избежать утечки ресурсов, даже если остановка не потребуется. - Для передачи значений используйте
context.WithValue, но для сигнала закрытия это не требуется - достаточно самого факта отмены.
На практике
При работе с пулом горутин (например, worker pool) контекст передаётся как первый аргумент в каждую функцию. Каждая горутина в своём цикле проверяет select на ctx.Done() и основной канал работы. При получении сигнала горутина завершает текущую операцию (если это возможно) и выходит.
Важно: если горутина блокируется на операции, которая не поддерживает контекст (например, системный вызов), нужно обернуть её в отдельную горутину или использовать таймаут. В реальном коде часто комбинируют context.WithCancel и sync.WaitGroup для ожидания завершения всех горутин после вызова cancel().
Для 15 горутин не нужно создавать 15 отдельных контекстов - один общий контекст передаётся всем. Если требуется остановить только часть горутин, создавайте отдельные дочерние контексты через context.WithCancel от общего.
Пример кода
GOpackage mainimport ("context""fmt""sync""time")func worker(ctx context.Context, id int, wg *sync.WaitGroup) {defer wg.Done()for {select {case <-ctx.Done():fmt.Printf("worker %d: stopping, reason: %v\n", id, ctx.Err())returndefault:// имитация работыfmt.Printf("worker %d: working\n", id)time.Sleep(100 * time.Millisecond)}}}func main() {ctx, cancel := context.WithCancel(context.Background())defer cancel() // гарантируем освобождение ресурсовvar wg sync.WaitGroupfor i := 1; i <= 15; i++ {wg.Add(1)go worker(ctx, i, &wg)}time.Sleep(500 * time.Millisecond)cancel() // сигнал всем горутинамwg.Wait() // ждём завершенияfmt.Println("all workers stopped")}
Как отвечать на собеседовании
Начните с краткого определения: context.WithCancel создаёт контекст и функцию отмены. Затем объясните механизм: cancel() закрывает канал Done(), и все горутины, слушающие его, получают сигнал. Подчеркните, что контекст - это кооперативный механизм: горутина сама должна проверять ctx.Done(), принудительного прерывания нет.
Упомяните, что для 15 горутин достаточно одного контекста, и что нужно использовать sync.WaitGroup для ожидания завершения. Расскажите про defer cancel() для предотвращения утечек. Если спросят про альтернативы - упомяните канал chan struct{}, но отметьте, что контекст стандартизирован и поддерживает таймауты и дедлайны.
Хорошо показать понимание edge case: если горутина блокируется на операции без поддержки контекста, нужно оборачивать её или использовать таймаут. Также можно упомянуть context.WithTimeout и context.WithDeadline как расширения базового паттерна.
Что проверяет интервьюер
Интервьюер оценивает:
- Понимание кооперативной природы отмены через контекст - что горутина сама должна реагировать на сигнал.
- Умение правильно создавать и освобождать контекст через
defer cancel(). - Знание паттерна graceful shutdown: сигнал → завершение работы → ожидание через
WaitGroup. - Понимание разницы между
context.Background()иcontext.TODO(). - Способность объяснить, почему контекст предпочтительнее простого канала: встроенная поддержка таймаутов, дедлайнов, значений и стандартизация в API.
- Внимание к деталям: например, что
ctx.Err()возвращает причину отмены (context.Canceledилиcontext.DeadlineExceeded).
Типичные ошибки
- Не вызывать
cancel()- утечка ресурсов, контекст никогда не будет отменён. - Вызывать
cancel()несколько раз - это безопасно, но бессмысленно; лучше вызывать один раз. - Забывать
wg.Wait()- программа завершится до остановки горутин. - Проверять
ctx.Done()только в начале функции, а не в цикле - горутина не остановится вовремя. - Использовать
context.WithValueдля сигнала отмены - это неверно, для этого естьDone(). - Передавать
context.Background()в каждую горутину вместо общего контекста - тогда отмена не сработает. - Блокироваться на операции без поддержки контекста - горутина зависнет, и
wg.Wait()будет ждать вечно.
> Похожие задачи по Go
Что происходит при обновлении нескольких строк в одном запросе в базе данных
На каком языке написан компилятор Go
Какие алгоритмы балансировки подходят для распределенных систем без синхронизации
Для чего используется рефлексия в Go
> Похожие задачи по backend
Что происходит при обновлении нескольких строк в одном запросе в базе данных
На каком языке написан компилятор Go
Какие алгоритмы балансировки подходят для распределенных систем без синхронизации
Для чего используется рефлексия в Go
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью