Sobes.tech
Middle

¿Cómo finalizar correctamente la ejecución de varias goroutines en Go?

sobes.tech AI

Respuesta de la IA

Existen varias formas comunes:

  • Uso del contexto (Context): Enfoque recomendado para cancelar operaciones en estructuras de árbol de llamadas. El contexto puede transmitir una señal de cancelación a través de un canal.

    package main
    
    import (
        "context"
        "fmt"
        "time"
    )
    
    func worker(ctx context.Context, id int) {
        for {
            select {
            case <-ctx.Done():
                fmt.Printf("Goroutine %d finaliza: %v\n", id, ctx.Err())
                return
            default:
                fmt.Printf("Goroutine %d trabajando...\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() // Enviamos la señal de cancelación
    
        time.Sleep(1 * time.Second) // Damos tiempo a las goroutines para terminar
        fmt.Println("Programa principal finalizado.")
    }
    
  • Uso de canales: Enviando una señal (por ejemplo, una estructura vacía struct{}) a través de un canal especial, se puede informar a la goroutine que debe terminar.

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func worker(stopChan <-chan struct{}, id int) {
        for {
            select {
            case <-stopChan:
                fmt.Printf("Goroutine %d finaliza por canal\n", id)
                return
            default:
                fmt.Printf("Goroutine %d trabajando...\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) // Cerrar el canal indica que debe terminar
    
        time.Sleep(1 * time.Second) // Damos tiempo a las goroutines para terminar
        fmt.Println("Programa principal finalizado.")
    }
    
  • Usando sync.WaitGroup (para esperar a que terminen, no para señalizar): WaitGroup se usa para esperar a que un grupo de goroutines termine, pero no proporciona un mecanismo de parada por sí mismo. A menudo se combina con contexto o canales.

    package main
    
    import (
        "context"
        "fmt"
        "sync"
        "time"
    )
    
    func worker(ctx context.Context, wg *sync.WaitGroup, id int) {
        defer wg.Done() // Disminuye el contador del WaitGroup al terminar
    
        for {
            select {
            case <-ctx.Done():
                fmt.Printf("Goroutine %d finaliza\n", id)
                return
            default:
                fmt.Printf("Goroutine %d trabajando...\n", id)
                time.Sleep(1 * time.Second)
            }
        }
    }
    
    func main() {
        ctx, cancel := context.WithCancel(context.Background())
        var wg sync.WaitGroup
    
        numWorkers := 2
        wg.Add(numWorkers) // Inicializamos el WaitGroup
    
        for i := 1; i <= numWorkers; i++ {
            go worker(ctx, &wg, i)
        }
    
        time.Sleep(3 * time.Second)
        cancel() // Señal de cancelación
    
        wg.Wait() // Esperamos a que terminen todas las goroutines
        fmt.Println("Programa principal finalizado.")
    }
    

La elección del método depende del caso de uso específico y de la complejidad de las interacciones entre goroutines. El contexto generalmente se prefiere para cancelaciones en cascada.