Go Concurrency

6 Kernkonzepte
Die wichtigsten Concurrency-Konzepte in Go: goroutine · channel · select · WaitGroup · Mutex · Worker Pool Go ist bekannt für seine eingebaute Concurrency-Unterstützung. Goroutinen sind leichtgewichtige Threads, Channels ermöglichen die sichere Kommunikation zwischen ihnen, und Synchronisationsprimitive wie WaitGroup und Mutex helfen bei der Koordination.

Goroutinen – Leichtgewichtige Threads

go func() · sync.WaitGroup
go myFunction() // Startet eine Goroutine go func(msg string) { fmt.Println(msg) }("Hallo")

Goroutinen sind leichtgewichtige, gleichzeitig ausgeführte Funktionen. Sie werden mit dem Schlüsselwort go gestartet und laufen unabhängig vom Hauptprogramm. Das Hauptprogramm endet, ohne auf die Goroutinen zu warten – daher oft in Kombination mit WaitGroup.

Beispiele
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
// Goroutine starten
go say("Welt")
say("Hallo")
// Goroutine mit anonyme Funktion
go func(x int) {
fmt.Println("x =", x)
}(42)
// Warten, damit Goroutinen Zeit haben
time.Sleep(1 * time.Second)
}
Tipp: Das Hauptprogramm beendet sich, ohne auf Goroutinen zu warten. Verwenden Sie sync.WaitGroup oder time.Sleep, um auf die Beendigung zu warten.

Channels – Kommunikation zwischen Goroutinen

make(chan T) · chan<- · <-chan
ch := make(chan string) // Unbuffered ch := make(chan string, 10) // Buffered ch <- "Hallo" // Senden msg := <-ch // Empfangen

Channels sind die primäre Kommunikationsmethode zwischen Goroutinen. Sie sind typisiert und können entweder ungepuffert (blockierend) oder gepuffert (asynchron) sein. Channels können geschlossen und mit range iteriert werden.

Beispiele
package main
import "fmt"
func sum(nums []int, ch chan int) {
sum := 0
for _, n := range nums {
sum += n
}
ch <- sum // Ergebnis senden
}
func main() {
nums := []int{1, 2, 3, 4, 5}
ch := make(chan int)
go sum(nums[:len(nums)/2], ch)
go sum(nums[len(nums)/2:], ch)
// Empfangen (blockiert)
x, y := <-ch, <-ch
fmt.Println(x + y)
// Gepufferter Channel
buffered := make(chan int, 2)
buffered <- 1
buffered <- 2
fmt.Println(<-buffered, <-buffered)
// Channel schließen
close(ch)
// Channel mit range iterieren
for v := range ch {
fmt.Println(v)
}
// Channel als Parameter (nur senden/empfangen)
func sendOnly(ch chan<- int) { /* ... */ }
func recvOnly(ch <-chan int) { /* ... */ }
}
Tipp: Verwenden Sie close(ch), um einen Channel zu schließen. Empfänger können prüfen, ob der Channel geschlossen wurde: v, ok := <-ch. Ein geschlossener Channel liefert den Zero-Value und ok == false.

select – Mehrere Channels abhören

select { case <-ch: ... }
select { case msg1 := <-ch1: fmt.Println(msg1) case msg2 := <-ch2: fmt.Println(msg2) case <-time.After(1 * time.Second): fmt.Println("Timeout") default: fmt.Println("Keine Nachricht") }

select blockiert, bis einer seiner case-Zweige bereit ist – ähnlich wie switch, aber für Channels. Es ermöglicht das gleichzeitige Abhören mehrerer Channels und ist ideal für Timeouts, Heartbeats und Multiplexing.

Beispiele
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(200 * time.Millisecond)
ch1 <- "von ch1"
}()
go func() {
time.Sleep(100 * time.Millisecond)
ch2 <- "von ch2"
}()
// select blockiert, bis ein Case bereit ist
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
case <-time.After(1 * time.Second):
fmt.Println("Timeout")
}
// select mit default (nicht-blockierend)
select {
case msg := <-ch1:
fmt.Println(msg)
default:
fmt.Println("Keine Nachricht verfügbar")
}
// select für Heartbeat
ticker := time.NewTicker(500 * time.Millisecond)
select {
case <-ticker.C:
fmt.Println("Tick")
case <-time.After(1 * time.Second):
fmt.Println("Timeout")
}
}
Tipp: select mit default ist nicht-blockierend – perfekt für nicht-blockierende Lese-/Schreiboperationen. Für Timeouts verwenden Sie time.After innerhalb eines case.

WaitGroup – Auf Goroutinen warten

Add · Done · Wait
var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() // Arbeit... }() wg.Wait() // Blockiert, bis alle Done aufgerufen haben

WaitGroup wird verwendet, um auf die Beendigung mehrerer Goroutinen zu warten. Es ist ein Zähler, der mit Add erhöht, mit Done verringert und mit Wait blockiert, bis der Zähler auf 0 ist.

Beispiele
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // Wird aufgerufen, wenn die Funktion beendet wird
fmt.Printf("Worker %d startet\n", id)
time.Sleep(1 * time.Second)
fmt.Printf("Worker %d fertig\n", id)
}
func main() {
var wg sync.WaitGroup
// Anzahl der Goroutinen festlegen
wg.Add(3)
for i := 1; i <= 3; i++ {
go worker(i, &wg)
}
// Auf alle Goroutinen warten
wg.Wait()
fmt.Println("Alle Arbeiter fertig")
}
Tipp: Add sollte aufgerufen werden, bevor die Goroutine gestartet wird. Verwenden Sie defer wg.Done() am Anfang der Goroutine, um sicherzustellen, dass sie immer aufgerufen wird – auch bei Panics.

Mutex – Gegenseitiger Ausschluss

Lock · Unlock · RWMutex
var mu sync.Mutex mu.Lock() // Kritischer Bereich mu.Unlock() // RWMutex für Lese-/Schreibzugriffe var rwmu sync.RWMutex rwmu.RLock() // Lesesperre rwmu.RUnlock()

Mutex (Mutual Exclusion) schützt gemeinsame Ressourcen vor gleichzeitigem Zugriff aus mehreren Goroutinen. Lock blockiert, bis die Sperre verfügbar ist, Unlock gibt sie frei. RWMutex erlaubt mehrere Leser, aber nur einen Schreiber.

Beispiele
package main
import (
"fmt"
"sync"
"time"
)
type Counter struct {
mu sync.Mutex
value int
}
func (c *Counter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++
}
func (c *Counter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}
func main() {
c := &Counter{}
var wg sync.WaitGroup
// 1000 Goroutinen erhöhen den Zähler
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
c.Increment()
}()
}
wg.Wait()
fmt.Println("Zähler:", c.Value()) // 1000
// RWMutex für Lese-/Schreibzugriffe
var rwmu sync.RWMutex
data := make(map[string]int)
// Lesen (mehrere gleichzeitig möglich)
rwmu.RLock()
val := data["key"]
rwmu.RUnlock()
// Schreiben (exklusiv)
rwmu.Lock()
data["key"] = 42
rwmu.Unlock()
}
Tipp: Verwenden Sie immer defer mu.Unlock() unmittelbar nach mu.Lock(), um Deadlocks zu vermeiden. Für häufige Lese-, seltene Schreibzugriffe ist RWMutex effizienter.

Worker Pool – Begrenzte Goroutinen

Channels · WaitGroup · Jobs
// Jobs und Ergebnisse über Channels jobs := make(chan int, 100) results := make(chan int, 100) // Worker-Funktion func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) { // ... }

Worker Pool ist ein Muster zur Begrenzung der Anzahl gleichzeitig laufender Goroutinen. Jobs werden über einen Channel verteilt, die Ergebnisse über einen weiteren Channel gesammelt. Ideal für CPU-intensive oder I/O-intensive Aufgaben.

Beispiele
package main
import (
"fmt"
"sync"
"time"
)
// Worker-Funktion
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
fmt.Printf("Worker %d verarbeitet Job %d\n", id, job)
time.Sleep(500 * time.Millisecond)
results <- job * 2
}
}
func main() {
const numJobs = 10
const numWorkers = 3
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
var wg sync.WaitGroup
// Worker starten
for i := 1; i <= numWorkers; i++ {
wg.Add(1)
go worker(i, jobs, results, &wg)
}
// Jobs senden
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
// Auf Worker warten
wg.Wait()
close(results)
// Ergebnisse sammeln
for result := range results {
fmt.Println("Ergebnis:", result)
}
}
Tipp: Der Worker-Pool ist ein Standardmuster für parallele Verarbeitung. Die Anzahl der Worker sollte an die verfügbaren CPU-Kerne oder die I/O-Kapazität angepasst werden. Ein zu großer Pool kann zu Ressourcenproblemen führen.

Go Concurrency im Überblick

go Goroutine
Leichtgewichtiger Thread
chan Channel
Kommunikation zwischen Goroutinen
select Multiplexing
Mehrere Channels abhören
WaitGroup Synchronisation
Auf Goroutinen warten
Mutex Gegenseitiger Ausschluss
Schützt gemeinsame Ressourcen
Worker Pool Begrenzte Goroutinen
Effiziente Parallelverarbeitung

Quick Summary

go
Goroutinen
chan
Channels
select
Multiplexing
WaitGroup
Synchronisation
Mutex
Sperren
Worker Pool
Parallelverarbeitung
go func() { ... }() · ch <- value · select { case <-ch: } · var wg sync.WaitGroup