7.2.5. Nebenläufigkeit und Context
Go macht Nebenläufigkeit leicht – und Leaks leicht. Jede Goroutine braucht eine klare Exit-Bedingung; Libraries starten sie selten „heimlich“ hinter einer normalen Funktion.
Ownership und Daten über Channels zu reichen, sodass nur eine Goroutine den Wert anfasst, ist der klassische Rat. Mutexes sind richtig, wenn sie klarer sind (z. B. ein einfacher Zähler). Proverbs: Channels orchestrate; mutexes serialize. Effective Go warnt davor, den Channel-Rat zu übertreiben.
jobs := make(chan Job)
go worker(jobs)
var (
mu sync.Mutex
n int
)
mu.Lock()
n++
mu.Unlock()
context.Context trägt Deadlines, Cancellation und request-scoped Werte über API-Grenzen.
func DoSomething(ctx context.Context, arg Arg) error {
// ... ctx nutzen ...
}
ctxexplizit die Aufrufkette hinunterreichen (meist erster Parameter).- Nicht in Structs speichern (als Methodenargument übergeben); seltene Ausnahme bei fremden Interfaces.
- Nie
nilContext; unsicher →context.TODO, Top-Level mit Grund →context.Background. - Abgeleitete Contexts mit
WithCancel/WithTimeout/WithDeadline; nach Gebrauch canceln. WithValuenur für request-scoped Daten über Prozess-/API-Grenzen – nicht für optionale Parameter.
Blockierte Goroutinen werden nicht vom GC eingesammelt. Exit-Bedingungen dokumentieren, wenn sie nicht offensichtlich sind. Bevorzugt synchrone APIs – der Caller steuert die Parallelität.
ctx, cancel := context.WithCancel(parent)
defer cancel()
done := make(chan struct{})
go func() {
defer close(done)
for {
select {
case <-ctx.Done():
return
case job := <-jobs:
handle(job)
}
}
}()
// später: cancel(); <-done
sync.WaitGroup.Go ist dem manuellen Add + go + defer Done vorzuziehen. Die gestartete Funktion darf nicht panicen.
Effective Go zeigt gepufferte Channels (z. B. Parallelisierung mit make(chan int, numCPU)). Uber bevorzugt ungepuffert oder Größe 1 und will jede andere Größe begründet sehen – das ist Org-Stil, keine Sprachnorm. In jedem Fall: Ownership und Backpressure klar halten.
Diese Seite gehört zu 7.2. Clean Code.