Материал проходит вычитку — возможны неточности.
Урок 116 из 118

Graceful shutdown пула

Ночной деплой порвал начатые загрузки: процесс убили, а воркеры не успели доделать начатое. Сергей потребовал, чтобы деплой больше не рвал чужие загрузки, и Миша сделал graceful shutdown — перестал принимать новое и дал начатому завершиться.

Главная мысль

graceful shutdown — перестать принимать новое, дать начатому завершиться: для этого перестают слать в tasks, дают воркерам дочитать канал и ждут их через WaitGroup.

Что значит «грациозно»

Graceful shutdown — это завершение без потерь: новые задачи не принимаются, а уже начатые доводятся до конца. Для пула это значит: производитель перестаёт отправлять в tasks, воркеры дочитывают канал (range сам закончится по закрытию), и главная горутина ждёт их через wg.Wait.

Остановка по сигналу

// производитель (wg.Add для воркеров выполнялся при их старте, L95):
loop:
for {
    select {
    case <-stop:          // сигнал остановки (ctx, SIGTERM)
        break loop
    case t, ok := <-input:
        if !ok {          // вход закрылся — то же, что сигнал остановки
            break loop
        }
        select {          // политика явная: принятую, но ещё не
        case tasks <- t:  // отправленную задачу доводим до канала
        case <-stop:      // ...или отбрасываем до отправки
            break loop
        }
    }
}

close(tasks) // новых задач не будет
wg.Wait()    // ждём, пока воркеры доработают очередь

В этом фрагменте два места, где код легко сломать. Приём без ok (case t:= <-input) на закрытом канале вечно отдаёт zero value: производитель зальёт tasks нулевыми задачами и никогда не дойдёт до остановки. А отправка tasks <- t вне select может надолго заблокировать производителя (все воркеры заняты), и он перестанет видеть stop. Поэтому приём — с ok, отправка — внутри select с веткой stop. Политика выбрана явно: задача, принятая из input, но ещё не попавшая в tasks, отбрасывается; «начатое доводится» касается только задач, уже отправленных в канал, — их дорабатывает close + range + wg.Wait.

Ключевая последовательность: сначала закрыть канал задач, потом ждать. Закрыть и не ждать — рискуешь выйти раньше, чем начатые задачи доделались: close(tasks) не прерывает работающую горутину, воркер доведёт текущий process до конца, но завершившийся main оборвёт его вместе с результатом. Гарантию «начатое доведено» даёт только wg.Wait. Если ждать, не закрыв — воркеры ждут задач вечно.

Что происходит с начатым

Воркер, читающий через range, после close(tasks) дорабатывает всё, что уже в канале, и только потом выходит — это свойство range по закрытому каналу, ничего специально «догонять» не нужно. Но договор «начатое доводится» верен при двух условиях. Первое: process обязан возвращаться — зависший воркер не выйдет из цикла никогда, и wg.Wait повиснет вечно, поэтому таймаут на доработку из расширения — штатная часть схемы, а не страховка на крайний случай. Второе: если у пула есть канал результатов, его нужно вычитывать (до или параллельно с wg.Wait) — иначе воркеры встанут на отправке результата и до wg.Done не дойдут.

В следующем уроке

Вы узнаете:

  • как таймаут на доработку работает
  • как signal.NotifyContext ловит SIGTERM/SIGINT
<!-- wp:course-hub/quiz {"title":"Проверьте себя","questions":[{"text":"Закрыли tasks, но не сделали wg.Wait() — main возвращается сразу. Что теряется?","options":[{"text":"Часть задач не обработана — выход из main обрывает горутины","correct":true,"comment":"Close не прерывает исполнение; завершённость гарантирует только wg.Wait()."},{"text":"Ничего — close всё доводит","correct":false},{"text":"Только лог","correct":false},{"text":"Паника","correct":false}]},{"text":"Приём case t := <-input без ok на закрытом канале. Что произойдёт?","options":[{"text":"Вечно отдаёт zero value — производитель зальёт tasks нулями","correct":true,"comment":"Приём должен быть с ok: case t, ok :=
Итоги

Graceful shutdown: перестать принимать новое → закрыть tasks → wg.Wait. Close не прерывает исполнение; завершённость начатого гарантирует только ожидание.

Наш сайт собирает куки. Чтобы тебе было удобнее им пользоваться. Нажимая на кнопку, ты даёшь разрешение собирать куки