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

Завершение множества горутин требует организованного подхода, так как управление ими не предоставляет прямых средств для их остановки. Основные практики включают использование каналов для сигнализации о необходимости завершения, контекстов для управления временем выполнения и ограничениями, а также синхронизации с помощью `sync.WaitGroup`. Вот каждый из этих методов.

1. Использование каналов для управления горутинами

Каналы могут использоваться для отправки сигналов горутинам о том, что им следует завершить свою работу. Это один из наиболее часто используемых подходов, так как он прост в реализации и очень эффективен.

```go
package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(stopCh <-chan struct{}, wg *sync.WaitGroup, id int) {
    defer wg.Done()
    for {
        select {
        case <-stopCh:
            fmt.Printf("Worker %d stopping\n", id)
            return
        default:
            // выполнение полезной работы
            fmt.Printf("Worker %d working\n", id)
            time.Sleep(time.Second)
        }
    }
}

func main() {
    var wg sync.WaitGroup
    stopCh := make(chan struct{})

    // запуск горутин
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go worker(stopCh, &wg, i)
    }

    // остановка горутин после 3 секунд
    time.Sleep(3 * time.Second)
    close(stopCh) // отправка сигнала всем горутинам остановиться
    wg.Wait()    // ожидание завершения всех горутин
}
```

2. Использование пакета `context`

Предоставляет функциональность для передачи контекста внутрь вашей программы, включая сигналы о необходимости завершения работы. Это может быть полезно, если у вас есть иерархия горутин с общим временем выполнения или дополнительными ограничениями.

```go
package main

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

func worker(ctx context.Context, wg *sync.WaitGroup, id int) {
    defer wg.Done()
    for {
        select {
        case <-ctx.Done():
            fmt.Printf("Worker %d stopping\n", id)
            return
        default:
            // выполнение полезной работы
            fmt.Printf("Worker %d working\n", id)
            time.Sleep(time.Second)
        }
    }
}

func main() {
    var wg sync.WaitGroup
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)

    // запуск горутин
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go worker(ctx, &wg, i)
    }

    wg.Wait() // ожидание завершения всех горутин
    cancel()  // убедиться, что все ресурсы освобождены
}
```

Для остановки множества горутин используются каналы или контексты. Оба метода позволяют элегантно и безопасно управлять жизненным циклом параллельных процессов, минимизируя риски вроде утечек памяти или "зомби" горутин. Ключевым моментом является выбор подхода, который лучше всего подходит для структуры и требований вашего приложения.

May 22, 2024, easyoffer