Как организовать отмену и завершение нескольких горутин?

Ответ

Идиоматичный и безопасный способ управления жизненным циклом нескольких горутин в Go — использование пакета context.

Основной подход: context.WithCancel и sync.WaitGroup

  1. context.Context используется для сигнализации горутинам о необходимости завершения. Создается родительский контекст с функцией отмены (cancel).
  2. sync.WaitGroup используется для того, чтобы основная горутина дождалась фактического завершения всех дочерних горутин после отправки сигнала отмены.

Это позволяет реализовать graceful shutdown (плавное завершение).

Пример:

func worker(ctx context.Context, wg *sync.WaitGroup, id int) {
    defer wg.Done()
    fmt.Printf("Воркер %d запущенn", id)
    select {
    case <-ctx.Done(): // Ожидаем сигнала отмены от контекста
        fmt.Printf("Воркер %d получил сигнал завершения.n", id)
        return
    case <-time.After(5 * time.Second): // Имитация работы
        fmt.Printf("Воркер %d завершил работу штатно.n", id)
    }
}

func main() {
    var wg sync.WaitGroup
    // Создаем контекст, который можно отменить
    ctx, cancel := context.WithCancel(context.Background())

    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go worker(ctx, &wg, i)
    }

    // Имитируем необходимость срочно завершить все горутины через 1 секунду
    time.Sleep(1 * time.Second)
    fmt.Println("Отправляем сигнал отмены всем воркерам...")
    cancel() // Отправляем сигнал

    wg.Wait() // Ожидаем, пока все горутины вызовут wg.Done()
    fmt.Println("Все воркеры завершены. Программа выключается.")
}

Аварийное завершение (panic)

Использование panic() приводит к каскадному завершению всех горутин и остановке программы (если panic не перехвачен с помощью recover). Это не способ управления, а механизм реакции на невосстановимую ошибку. Не используйте panic для штатного завершения горутин.