Sobes.tech
Middle

Πώς να ολοκληρώσετε σωστά την εκτέλεση πολλαπλών goroutines στο Go;

sobes.tech AI

Απάντηση από AI

Υπάρχουν μερικοί συνηθισμένοι τρόποι:

  • Χρήση του context: Η προτεινόμενη προσέγγιση για ακύρωση λειτουργιών σε δομή δέντρου κλήσεων. Το 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 χρησιμοποιείται για την αναμονή της ολοκλήρωσης μιας ομάδας γουρουίντων, αλλά από μόνο του δεν παρέχει μηχανισμό διακοπής. Συχνά χρησιμοποιείται σε συνδυασμό με context ή κανάλια.

    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("Το κύριο πρόγραμμα ολοκληρώθηκε.")
    }
    

Η επιλογή εξαρτάται από την συγκεκριμένη περίπτωση χρήσης και την πολυπλοκότητα των αλληλεπιδράσεων των γουρουίντων. Το context προτιμάται γενικά για διαδοχικές ακυρώσεις.