Sobes.tech
Middle

Como terminar corretamente a execução de várias goroutines em Go?

sobes.tech IA

Resposta da IA

Existem várias formas comuns:

  • Utilização do contexto (Context): Abordagem recomendada para cancelar operações em estruturas de árvore de chamadas. O contexto pode transmitir um sinal de cancelamento através de um canal.

    package main
    
    import (
        "context"
        "fmt"
        "time"
    )
    
    func worker(ctx context.Context, id int) {
        for {
            select {
            case <-ctx.Done():
                fmt.Printf("Goroutine %d a terminar: %v\n", id, ctx.Err())
                return
            default:
                fmt.Printf("Goroutine %d a trabalhar...\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() // Envia o sinal de cancelamento
    
        time.Sleep(1 * time.Second) // Dá tempo às goroutines para terminar
        fmt.Println("Programa principal terminado.")
    }
    
  • Utilização de canais: Enviando um sinal (por exemplo, uma estrutura vazia struct{}) através de um canal especial, pode-se informar à goroutine que ela deve terminar.

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func worker(stopChan <-chan struct{}, id int) {
        for {
            select {
            case <-stopChan:
                fmt.Printf("Goroutine %d a terminar por canal\n", id)
                return
            default:
                fmt.Printf("Goroutine %d a trabalhar...\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) // Fechar o canal indica que deve terminar
    
        time.Sleep(1 * time.Second) // Dá tempo às goroutines para terminar
        fmt.Println("Programa principal terminado.")
    }
    
  • Usando sync.WaitGroup (para esperar que terminem, não para sinalizar): WaitGroup é usado para esperar que um grupo de goroutines termine, mas não fornece um mecanismo de parada por si só. É frequentemente combinado com contexto ou canais.

    package main
    
    import (
        "context"
        "fmt"
        "sync"
        "time"
    )
    
    func worker(ctx context.Context, wg *sync.WaitGroup, id int) {
        defer wg.Done() // Diminui o contador do WaitGroup ao terminar a goroutine
    
        for {
            select {
            case <-ctx.Done():
                fmt.Printf("Goroutine %d a terminar\n", id)
                return
            default:
                fmt.Printf("Goroutine %d a trabalhar...\n", id)
                time.Sleep(1 * time.Second)
            }
        }
    }
    
    func main() {
        ctx, cancel := context.WithCancel(context.Background())
        var wg sync.WaitGroup
    
        numWorkers := 2
        wg.Add(numWorkers) // Inicializa o WaitGroup
    
        for i := 1; i <= numWorkers; i++ {
            go worker(ctx, &wg, i)
        }
    
        time.Sleep(3 * time.Second)
        cancel() // Sinal de cancelamento
    
        wg.Wait() // Espera que todas as goroutines terminem
        fmt.Println("Programa principal terminado.")
    }
    

A escolha do método depende do caso de uso específico e da complexidade das interações entre goroutines. O contexto é geralmente preferido para cancelamentos em cascata.