Menu
Coddy logo textTech

sync.WaitGroup

Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 64 von 107.

In der Lektion „Grundlagen der Goroutinen“ haben wir time.Sleep verwendet, um auf das Beenden von Goroutinen zu warten: ein anfälliger Ansatz. sync.WaitGroup bietet eine geeignete Möglichkeit, darauf zu warten, dass eine Sammlung von Goroutinen ihre Arbeit abschließt.

Ein WaitGroup funktioniert wie ein Zähler. Du erhöhst ihn, bevor du eine Goroutine startest, verringerst ihn, wenn die Goroutine beendet ist, und blockierst, bis der Zähler null erreicht:

func main() {
    var wg sync.WaitGroup
    
    for i := 1; i <= 3; i++ {
        wg.Add(1)  // Zähler erhöhen
        go func(id int) {
            defer wg.Done()  // verringern wenn fertig
            fmt.Printf("Worker %d finished\n", id)
        }(i)
    }
    
    wg.Wait()  // blockieren bis Zähler 0 ist
    fmt.Println("All workers complete")
}

Die drei wichtigsten Methoden sind Add(n), um den Zähler um n zu erhöhen, Done(), um ihn um eins zu verringern (entspricht Add(-1)), und Wait(), um zu blockieren, bis der Zähler null erreicht. Die Verwendung von defer wg.Done() stellt sicher, dass der Zähler auch dann verringert wird, wenn die Goroutine in Panik gerät.

Ein häufiger Fehler besteht darin, Add innerhalb der Goroutine statt davor aufzurufen. Dadurch entsteht eine Race Condition, bei der Wait zurückkehren könnte, bevor alle Goroutinen registriert sind:

// Falsch – Race Condition
go func() {
    wg.Add(1)  // könnte nach Wait() laufen
    defer wg.Done()
}()

// Richtig – Add vor dem Starten
wg.Add(1)
go func() {
    defer wg.Done()
}()

WaitGroups werden häufig in Strukturen eingebettet, um nebenläufige Vorgänge zu koordinieren, wodurch sie für die Erstellung threadsicherer Typen in Go unverzichtbar sind.

challenge icon

Aufgabe

Einfach

Erstellen wir einen Download-Manager, der mehrere gleichzeitig ausgeführte Dateidownloads mithilfe von sync.WaitGroup koordiniert. Dein Manager verfolgt, wann alle Downloads abgeschlossen sind, ohne auf willkürliche Sleep-Timer angewiesen zu sein.

Du organisierst deinen Code über zwei Dateien:

  • downloader.go: Definiere hier die Logik zur Koordination der Downloads.

    Erstelle eine Download-Struktur mit den Feldern ID (int), Filename (string) und Size (int, in KB).

    Implementiere eine Methode Process(wg *sync.WaitGroup) für Download, die einen Download simuliert, indem sie für eine von der Dateigröße abhängige Dauer pausiert (verwende Size * 10 Millisekunden). Wenn der Download abgeschlossen ist, gib Folgendes aus: Downloaded: [Filename] ([Size]KB). Verwende defer wg.Done(), um sicherzustellen, dass der Zähler der WaitGroup ordnungsgemäß verringert wird.

    Erstelle eine Funktion StartDownloads(downloads []Download) int, die alle Downloads gleichzeitig mithilfe von Goroutinen startet und wartet, bis sie alle abgeschlossen sind. Denke daran, vor dem Start jeder Goroutine wg.Add(1) aufzurufen, nicht innerhalb der Goroutine. Gib die kombinierte Gesamtgröße aller Downloads zurück.

  • main.go: Lies die Downloadinformationen ein und koordiniere die gleichzeitigen Downloads.

    Lies die Anzahl der Downloads ein und lies anschließend für jeden Download seine ID, seinen Dateinamen und seine Größe ein. Erstelle die Downloads und übergib sie an StartDownloads. Gib nach Abschluss aller Downloads Folgendes aus: All downloads complete: [total]KB total

Die folgenden Eingaben werden bereitgestellt:

  • Zeile 1: Anzahl der Downloads (Ganzzahl)
  • Folgende Zeilen: Für jeden Download drei Zeilen – die ID (Ganzzahl), der Dateiname (String) und die Größe in KB (Ganzzahl)

Zum Beispiel bei folgender Eingabe:

3
1
report.pdf
20
2
image.png
5
3
data.csv
10

Deine Ausgabe sollte zeigen, dass die Downloads abgeschlossen werden (kleinere Dateien werden aufgrund der kürzeren Pausenzeiten zuerst fertig), gefolgt von der Zusammenfassung:

Downloaded: image.png (5KB)
Downloaded: data.csv (10KB)
Downloaded: report.pdf (20KB)
All downloads complete: 35KB total

Der wesentliche Unterschied zur Verwendung von time.Sleep besteht darin, dass wg.Wait() blockiert, bis alle Goroutinen mit Done() ihre Fertigstellung signalisieren. Dadurch erhältst du eine präzise Synchronisierung, unabhängig davon, wie lange die einzelnen Downloads dauern.

Probier es selbst

package main

import (
	"fmt"
)

func main() {
	// Lese die Anzahl der Downloads
	var n int
	fmt.Scanln(&n)

	// Lese Download-Informationen
	downloads := make([]Download, n)
	for i := 0; i < n; i++ {
		var id int
		var filename string
		var size int
		fmt.Scanln(&id)
		fmt.Scanln(&filename)
		fmt.Scanln(&size)
		
		downloads[i] = Download{
			ID:       id,
			Filename: filename,
			Size:     size,
		}
	}

	// TODO: Rufe StartDownloads mit dem downloads-Slice auf
	// TODO: Print the summary: "All downloads complete: [total]KB total"
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Online-Go-Compiler