⥠1. Was sind Goroutines?
Eine Goroutine ist ein extrem leichtgewichtiger AusfĂŒhrungsfaden, der von der Go-Runtime verwaltet wird (M:N-Multiplexing auf OS-Threads). WĂ€hrend ein nativer OS-Thread ca. 1–2 MB Stack-Speicher reserviert, startet eine Goroutine mit nur ca. 2 KB dynamischem Stack. Dadurch kann ein einzelnes Go-Programm problemlos 100.000+ Goroutines gleichzeitig ausfĂŒhren!
ââââ go Worker(2) âââ> [Worker 2 lĂ€uft asynchron] âââŒââ> wg.Wait() (Alle fertig!)
ââââ go Worker(3) âââ> [Worker 3 lĂ€uft asynchron] âââ
package main
import (
"fmt"
"time"
)
func sayHello(id int) {
fmt.Printf("Worker %d meldet sich zum Dienst!\n", id)
}
func main() {
// đŻ Goroutine starten mit SchlĂŒsselwort 'go':
go sayHello(1)
go sayHello(2)
// ACHTUNG: Ohne Synchronisation beendet sich main(), bevor Goroutines laufen!
time.Sleep(10 * time.Millisecond) // Schlechter Stil â nutze sync.WaitGroup!
}
đĄïž 2. Saubere Synchronisation mit sync.WaitGroup
Verwende niemals time.Sleep() zum Warten auf Goroutines. Die Standardbibliothek bietet dafĂŒr sync.WaitGroup mit exakt drei Methoden:
wg.Add(n)
Erhöht den internen ZÀhler um die Anzahl geplanter Goroutines.
defer wg.Done()
Dekrementiert den ZĂ€hler um 1, sobald eine Goroutine fertig ist.
wg.Wait()
Blockiert den Aufrufer (z.B. main), bis der ZĂ€hler exakt 0 erreicht.
package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // Signalisiert Fertigstellung beim Verlassen der Funktion
fmt.Printf("Worker %d abgeschlossen\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
// â ïž WICHTIG: WaitGroup IMMER als Pointer (*sync.WaitGroup) ĂŒbergeben!
go worker(i, &wg)
}
wg.Wait() // Wartet deterministisch auf alle 3 Worker
fmt.Println("Alle Worker erfolgreich beendet!")
}
Stell dir sync.WaitGroup wie eine digitale Stempeluhr vor:
Vor Arbeitsbeginn bucht der Bauleiter 5 Handwerker ein (wg.Add(5)). Jeder Handwerker arbeitet eigenstĂ€ndig in seinem Raum. Sobald ein Handwerker fertig ist, stempelt er aus (wg.Done()). Der Bauleiter wartet am Ausgang (wg.Wait()) und schlieĂt die Baustelle erst ab, wenn die Uhr genau 0 verbleibende Handwerker anzeigt.
đ 3. Cheat-Sheet: Best Practices fĂŒr Goroutines
| Regel / Pattern | Richtig (Idiomatisch) | Falsch (Anti-Pattern) |
|---|---|---|
| Pointer bei WaitGroup | func work(wg *sync.WaitGroup) |
func work(wg sync.WaitGroup) (Kopie fĂŒhrt zu Deadlock!) |
| Loop Variable Passing | go func(val int) { ... }(i) |
go func() { use(i) }() (Race in Go < 1.22) |
| Done via defer | defer wg.Done() ganz oben im Worker |
wg.Done() am Ende (kann bei Panic ĂŒbersprungen werden) |
| Add-Aufruf vor Goroutine | wg.Add(1) vor dem go-Statement |
wg.Add(1) innerhalb der Goroutine (Race mit Wait) |
đŻ Aufgaben & Checkliste in aufgabe.go
Jede gestartete Goroutine muss einen definierten Weg zur Beendigung haben. Eine Goroutine, die fĂŒr immer auf einen unbedienten Channel oder ein Lock wartet, belegt dauerhaft Speicher (Goroutine Leak).