9 Fortgeschrittene Concurrency-Techniken

9.1 Synchronisation mit WaitGroups

Eine häufige Anforderung bei der Arbeit mit Concurrency ist es, sicherzustellen, dass mehrere Goroutines beendet sind, bevor das Hauptprogramm fortfährt. Dafür verwendet Go das Paket sync und speziell die WaitGroup.

9.1.1 Verwendung von WaitGroups

Hier ein Beispiel, wie WaitGroup verwendet wird:

package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Printf("Worker %d starting\n", id)
    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go worker(i, &wg)
    }

    wg.Wait()
}

In diesem Beispiel:

  1. Wir definieren eine Funktion worker, die eine Nachricht ausgibt, eine Sekunde schläft und dann eine andere Nachricht ausgibt. Jede worker-Funktion nimmt eine WaitGroup-Referenz als Parameter und ruft wg.Done() auf, wenn sie fertig ist.
  2. Im main-Programm erstellen wir eine WaitGroup-Instanz.
  3. Wir starten drei Goroutines, die jeweils die worker-Funktion ausführen, und fügen jede von ihnen zur WaitGroup hinzu, indem wir wg.Add(1) aufrufen.
  4. Schließlich rufen wir wg.Wait() auf, um darauf zu warten, dass alle Goroutines beendet sind, bevor das Programm fortfährt.

9.2 Mutex zur Vermeidung von Datenrennen

Ein weiteres wichtiges Konzept in der Concurrency ist der Schutz von gemeinsam genutzten Ressourcen, um Datenrennen zu vermeiden. Dafür verwendet Go das sync-Paket und den Mutex.

9.2.1 Verwendung von Mutexen

Hier ein Beispiel, wie Mutex verwendet wird:

package main

import (
    "fmt"
    "sync"
)

type Counter struct {
    mu    sync.Mutex
    count int
}

func (c *Counter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count++
}

func (c *Counter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

func main() {
    var wg sync.WaitGroup
    counter := Counter{}

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for j := 0; j < 1000; j++ {
                counter.Increment()
            }
        }()
    }

    wg.Wait()
    fmt.Println("Final count:", counter.Value())
}

In diesem Beispiel:

  1. Wir definieren eine Counter-Struktur, die einen Mutex und einen Zähler enthält.
  2. Die Increment-Methode sperrt den Mutex, erhöht den Zähler und entsperrt den Mutex wieder. Dasdefer-Schlüsselwort sorgt dafür, dass derMutex` entsperrt wird, sobald die Methode beendet ist.
  3. Die Value-Methode sperrt den Mutex, gibt den aktuellen Wert des Zählers zurück und entsperrt den Mutex.
  4. Im main-Programm erstellen wir fünf Goroutines, die jeweils tausend Mal die Increment-Methode aufrufen. Wir verwenden eine WaitGroup, um sicherzustellen, dass alle Goroutines abgeschlossen sind, bevor wir den endgültigen Zählerwert ausgeben.