Sobes.tech
Middle

Kako pravilno završiti izvršavanje više gorutina u Go?

sobes.tech АИ

Одговор од АИ

Постоји неколико уобичајених начина:

  • Коришћење контекста (Context): Препоручени приступ за отказивање операција у дрвовитој структури позива. Контекст може преносити сигнал отказа преко канала.

    package main
    
    import (
        "context"
        "fmt"
        "time"
    )
    
    func worker(ctx context.Context, id int) {
        for {
            select {
            case <-ctx.Done():
                fmt.Printf("Горутинa %d се завршава: %v\n", id, ctx.Err())
                return
            default:
                fmt.Printf("Горутинa %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) // Дамо време горутинaма да се заврше
        fmt.Println("Главни програм је завршен.")
    }
    
  • Користећи канале: Пошаљите сигнал (нпр. празну структуру struct{}) преко специјалног канала, што може обавестити горутину о потреби за завршетком.

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func worker(stopChan <-chan struct{}, id int) {
        for {
            select {
            case <-stopChan:
                fmt.Printf("Горутинa %d се завршава преко канала\n", id)
                return
            default:
                fmt.Printf("Горутинa %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) // Дамо време горутинaма да се заврше
        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("Горутинa %d се завршава\n", id)
                return
            default:
                fmt.Printf("Горутинa %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("Главни програм је завршен.")
    }
    

Избор метода зависи од конкретног случаја употребе и сложености интеракција горутиних. Контекст је генерално пожељан за каскадне отказе.