Sobes.tech
Middle

Как правилно да завършите изпълнението на няколко горутини в Go?

sobes.tech AI

Отговор от 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("Основната програма е приключила.")
    }
    
  • Използване на 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("Основната програма е приключила.")
    }
    

Изборът на метод зависи от конкретния случай и сложността на взаимодействията между горутините. Обикновено за каскадни отменяния се предпочита контекст.