Hintergrund: Kanäle sind ein fundamentales Konzept des CSP-Modells in Go. Sie sind für den Datenaustausch zwischen Goroutinen gedacht. Die Arbeit mit Kanälen erfordert besondere Vorsicht, um Deadlocks, Speicherlecks und Paniken beim Schließen zu vermeiden.
Problem: Das Schließen und die weitere Verwendung von Kanälen führen oft zu Panik (panic: send on closed channel). Die Arbeit mit nil-Kanälen oder der Versuch, aus einem geschlossenen Kanal zu lesen, ohne zu prüfen, kann unerwartete Ergebnisse liefern. Verschiedene Goroutinen können gleichzeitig in einen Kanal schreiben und daraus lesen, was eine klar definierte Logik des Lebenszyklus erfordert.
Lösung: Ein Kanal sollte immer nur von der Seite geschlossen werden, von der niemand mehr in den Kanal schreibt (normalerweise der Produzent). Nach dem Schließen können Werte aus dem Kanal gelesen werden, bis er leer ist, aber Schreiben ist nicht erlaubt – es wird eine Panik ausgelöst. Das Überprüfen des Schließens ist bequem über den zweiten Rückgabewert aus dem Kanal:
Beispielcode:
ch := make(chan int) go func() { for i := 0; i < 5; i++ { ch <- i } close(ch) }() for v := range ch { fmt.Println(v) }
Wichtige Eigenschaften:
1. Muss ein Kanal geschlossen werden, wenn nur eine Goroutine davon liest und niemand auf zusätzliche Werte wartet?
Antwort: Es ist nicht notwendig, den Kanal zu schließen, wenn niemand auf dessen Schließung über range wartet. Aber wenn die Schleife for v := range ch verwendet wird – ja, der Kanal muss geschlossen werden, sonst wird die Schleife nicht enden.
2. Was passiert, wenn man aus einem geschlossenen Kanal liest?
Antwort: Werte werden zurückgegeben, bis der Puffer leer ist, danach – Nullwert und Indikator false. Es ist sicher, bis zum Ende nach dem Schließen aus dem Kanal zu lesen.
ch := make(chan int, 1) ch <- 42 close(ch) v, ok := <-ch // v = 42, ok = true v, ok = <-ch // v = 0, ok = false
3. Wer sollte den Kanal schließen: der Leser oder der Schreiber?
Antwort: Immer der "Schreiber" (der, der Werte sendet und weiß, wann die Arbeit beendet ist). Der "Leser" sollte den Kanal nicht schließen, um ein Rennen zu vermeiden.
In der Anwendung schreiben mehrere Goroutinen in einen Kanal, eine liest. Jemand hat den Kanal im Leser geschlossen, während eine andere Goroutine noch versucht hat zu senden – Panik.
Vorteile: Bedingt einfache Logik des Austauschs. Nachteile: Unmöglichkeit, den Schließzeitpunkt vorherzusagen, Paniken, Rennbedingungen.
Der Schreiber verfolgt alle Goroutinen, verwendet sync.WaitGroup, schließt den Kanal nur nach Abschluss aller Schreiber. Der Leser beendet die Schleife range korrekt nach dem Schließen des Kanals und behandelt Fehler sicher.
Vorteile: Korrekte Synchronisation, keine Lecks und Deadlocks, vorhersehbares Verhalten. Nachteile: Etwas komplexere Logik, alle Arbeiten müssen explizit abgeschlossen werden.