← Назад к вопросам

Как остановить выполнение всех горутин?

1.3 Junior🔥 61 комментариев
#Конкурентность и горутины#Основы Go

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Полное руководство по остановке горутин в Go

Остановка выполнения горутин — критически важная задача в Go, поскольку неконтролируемые горутины могут приводить к утечкам памяти, зависанию приложений и другим проблемам. Вот наиболее эффективные подходы:

1. Использование каналов для сигнализации

Самый идиоматичный способ — использование канала done для передачи сигнала остановки:

package main

import (
    "context"
    "fmt"
    "time"
)

func worker(done <-chan struct{}, id int) {
    for {
        select {
        case <-done:
            fmt.Printf("Worker %d остановлен\n", id)
            return
        default:
            fmt.Printf("Worker %d работает...\n", id)
            time.Sleep(500 * time.Millisecond)
        }
    }
}

func main() {
    done := make(chan struct{})
    
    // Запускаем несколько горутин
    for i := 1; i <= 3; i++ {
        go worker(done, i)
    }
    
    // Даём поработать 2 секунды
    time.Sleep(2 * time.Second)
    
    // Останавливаем все горутины
    close(done)
    
    // Даём время на завершение
    time.Sleep(1 * time.Second)
    fmt.Println("Все горутины остановлены")
}

2. Использование Context (рекомендуемый способ)

Пакет context предоставляет наиболее мощный и стандартизированный механизм:

func workerWithContext(ctx context.Context, id int) {
    for {
        select {
        case <-ctx.Done():
            fmt.Printf("Worker %d остановлен по контексту: %v\n", id, ctx.Err())
            return
        case <-time.After(500 * time.Millisecond):
            fmt.Printf("Worker %d работает...\n", id)
        }
    }
}

func main() {
    // Создаём контекст с отменой
    ctx, cancel := context.WithCancel(context.Background())
    
    // Запускаем горутины
    for i := 1; i <= 3; i++ {
        go workerWithContext(ctx, i)
    }
    
    time.Sleep(2 * time.Second)
    
    // Отменяем контекст - остановит ВСЕ горутины, его использующие
    cancel()
    
    time.Sleep(1 * time.Second)
}

3. WaitGroup для синхронизированной остановки

sync.WaitGroup позволяет дождаться завершения всех горутин:

import "sync"

func controlledWorker(wg *sync.WaitGroup, stop <-chan bool, id int) {
    defer wg.Done()
    
    for {
        select {
        case <-stop:
            fmt.Printf("Worker %d получил сигнал остановки\n", id)
            return
        default:
            fmt.Printf("Worker %d выполняет задачу\n", id)
            time.Sleep(300 * time.Millisecond)
        }
    }
}

func main() {
    var wg sync.WaitGroup
    stop := make(chan bool)
    
    // Запускаем 5 горутин
    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go controlledWorker(&wg, stop, i)
    }
    
    time.Sleep(2 * time.Second)
    
    // Закрываем канал stop для всех горутин
    close(stop)
    
    // Ждём завершения всех горутин
    wg.Wait()
    fmt.Println("Все горутины завершились")
}

4. Таймауты и дедлайны через Context

func main() {
    // Автоматическая отмена через 3 секунды
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel() // Важно вызывать cancel для освобождения ресурсов
    
    // Контекст с дедлайном
    deadlineCtx, deadlineCancel := context.WithDeadline(
        context.Background(),
        time.Now().Add(5*time.Second),
    )
    defer deadlineCancel()
}

5. Комбинированный подход (наиболее надёжный)

type WorkerPool struct {
    workers []func()
    stop    chan struct{}
    wg      sync.WaitGroup
    ctx     context.Context
    cancel  context.CancelFunc
}

func (wp *WorkerPool) StartWorker(work func(ctx context.Context)) {
    wp.wg.Add(1)
    go func() {
        defer wp.wg.Done()
        work(wp.ctx)
    }()
}

func (wp *WorkerPool) StopAll() {
    // 1. Отменяем контекст
    wp.cancel()
    
    // 2. Закрываем канал stop
    close(wp.stop)
    
    // 3. Ждём завершения всех горутин
    wp.wg.Wait()
}

Ключевые принципы и лучшие практики:

  1. Всегда используйте defer для очистки ресурсов — гарантирует выполнение даже при панике
  2. Context — предпочтительный способ для передачи сигналов отмены в Go-приложениях
  3. Избегайте грубых методов типа runtime.Goexit() — они нарушают поток управления
  4. Graceful shutdown — давайте горутинам время на корректное завершение
  5. Проектируйте горутины с возможностью остановки с самого начала
  6. Никогда не останавливайте горутины извне принудительно — это нарушает принципы Go
  7. Используйте пулы воркеров для управления группами горутин

Чего следует избегать:

// НЕПРАВИЛЬНО - так делать нельзя!
var globalStopFlag bool // Гонки данных!

// НЕПРАВИЛЬНО - может привести к утечкам
go func() {
    for {
        // Бесконечный цикл без возможности остановки
    }
}()

// ОПАСНО - только для крайних случаев
import "runtime"
runtime.Goexit() // Останавливает текущую горутину

Правильное управление жизненным циклом горутин — основа создания надёжных и эффективных Go-приложений. Комбинация context, каналов и WaitGroup покрывает 99% всех сценариев, обеспечивая безопасное и контролируемое завершение работы.