Назад к вопросам
Middle
153
questionbank

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

Sobes Copilot

Получайте ответы в реальном времени

Ответ от нейросети

sobes.tech AI

Существуют несколько распространенных способов:

  • Использование контекста (Context): Рекомендуемый подход для отмены операций в древовидной структуре вызовов. Контекст может передавать сигнал отмены через канал.

    package main
    
    import (
        "context"
        "fmt"
        "time"
    )
    
    func worker(ctx context.Context, id int) {
        for {
            select {
            case <-ctx.Done():
                fmt.Printf("Горутина %d завершается: %v\n", id, ctx.Err())
                return
            default:
                fmt.Printf("Горутина %d работает...\n", id)
                time.Sleep(1 * time.Second)
            }
        }
    }
    
    func main() {
        ctx, cancel := context.WithCancel(context.Background())
    
        go worker(ctx, 1)
        go worker(ctx, 2)
    
        time.Sleep(3 * time.Second)
        cancel() // Отправляем сигнал отмены
    
        time.Sleep(1 * time.Second) // Даем время горутинам завершиться
        fmt.Println("Основная программа завершена.")
    }
    
  • Использование каналов: Отправив сигнал (например, пустую структуру struct{}) по специальному каналу, можно сообщить горутине о необходимости завершения.

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func worker(stopChan <-chan struct{}, id int) {
        for {
            select {
            case <-stopChan:
                fmt.Printf("Горутина %d завершается по каналу\n", id)
                return
            default:
                fmt.Printf("Горутина %d работает...\n", id)
                time.Sleep(1 * time.Second)
            }
        }
    }
    
    func main() {
        stopChan := make(chan struct{})
    
        go worker(stopChan, 1)
        go worker(stopChan, 2)
    
        time.Sleep(3 * time.Second)
        close(stopChan) // Закрытие канала сигнализирует о завершении
    
        time.Sleep(1 * time.Second) // Даем время горутинам завершиться
        fmt.Println("Основная программа завершена.")
    }
    
  • Using sync.WaitGroup (для ожидания завершения, а не для сигнализации): WaitGroup используется для ожидания завершения группы горутин, но сам по себе не предоставляет механизма остановки. Его часто применяют в сочетании с контекстом или каналами.

    package main
    
    import (
        "context"
        "fmt"
        "sync"
        "time"
    )
    
    func worker(ctx context.Context, wg *sync.WaitGroup, id int) {
        defer wg.Done() // Уменьшаем счетчик WaitGroup при завершении горутины
    
        for {
            select {
            case <-ctx.Done():
                fmt.Printf("Горутина %d завершается\n", id)
                return
            default:
                fmt.Printf("Горутина %d работает...\n", id)
                time.Sleep(1 * time.Second)
            }
        }
    }
    
    func main() {
        ctx, cancel := context.WithCancel(context.Background())
        var wg sync.WaitGroup
    
        numWorkers := 2
        wg.Add(numWorkers) // Инициализируем WaitGroup
    
        for i := 1; i <= numWorkers; i++ {
            go worker(ctx, &wg, i)
        }
    
        time.Sleep(3 * time.Second)
        cancel() // Сигнал отмены
    
        wg.Wait() // Ждем завершения всех горутин
        fmt.Println("Основная программа завершена.")
    }
    

Выбор методаDepends on the specific use case and the complexity of the goroutine interactions. Context is generally preferred for cascading cancellations.