Channels & Kommunikation
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 60 von 107.
Kanäle sind Gos wichtigster Mechanismus für die Kommunikation zwischen goroutines. Anstatt Speicher direkt gemeinsam zu nutzen, senden und empfangen goroutines Werte über Kanäle, ganz im Sinne der Philosophie von Go: „Kommuniziere nicht durch das Teilen von Speicher; teile Speicher durch Kommunikation.“
Erstelle einen Kanal mit der Funktion make und dem Schlüsselwort chan:
messages := make(chan string)Verwende den Pfeiloperator <-, um Werte zu senden und zu empfangen. Der Pfeil zeigt in die Richtung des Datenflusses:
func main() {
messages := make(chan string)
go func() {
messages <- "hello" // an Kanal senden
}()
msg := <-messages // von Kanal empfangen
fmt.Println(msg) // Ausgabe: hello
}Standardmäßig senden und empfangen sie blockierend, bis die andere Seite bereit ist. Wenn die Goroutine "hello" sendet, wartet sie, bis main zum Empfangen bereit ist. Dieses blockierende Verhalten bietet eine integrierte Synchronisierung: Kein time.Sleep erforderlich.
Du kannst einen Kanal schließen, um zu signalisieren, dass keine weiteren Werte gesendet werden. Empfänger können dies erkennen:
close(messages)
msg, ok := <-messages
if !ok {
fmt.Println("Channel closed")
}Channels sind typisiert: Ein chan string kann nur Zeichenketten übertragen. Diese Typsicherheit stellt sicher, dass Goroutinen mit den richtigen Datentypen kommunizieren, und erkennt Fehler zur Kompilierzeit statt zur Laufzeit.
Aufgabe
EinfachErstellen wir ein Nachrichtenweiterleitungssystem, das demonstriert, wie Goroutinen über Kanäle kommunizieren. Du erstellst eine Pipeline, in der Nachrichten zwischen verschiedenen Verarbeitungsstufen fließen, wobei jede Stufe gleichzeitig ausgeführt wird.
Du organisierst deinen Code über zwei Dateien:
relay.go: Definiere deine Nachrichtenverarbeitungspipeline.Erstelle eine
Message-Struktur mit den FeldernID(int) undContent(string).Implementiere drei Funktionen, die verschiedene Stufen deiner Pipeline darstellen:
Producer(messages []Message, out chan Message)– Nimmt ein Nachrichten-Slice entgegen und sendet jede Nachricht an den Ausgabekanal. Nachdem alle Nachrichten gesendet wurden, schließe den Kanal, um den Abschluss zu signalisieren.Transformer(in chan Message, out chan Message)– Empfängt Nachrichten vom Eingabekanal, wandelt den Inhalt jeder Nachricht in Großbuchstaben um und sendet die transformierte Nachricht an den Ausgabekanal. Wenn der Eingabekanal geschlossen ist (erkannt mithilfe der Empfangsform mit zwei Werten), schließe den Ausgabekanal.Consumer(in chan Message) []string– Empfängt alle Nachrichten vom Eingabekanal und sammelt sie in einem Slice formatierter Zeichenketten. Jede Zeichenkette sollte dem FormatMessage [ID]: [Content]folgen. Gib das Slice zurück, wenn der Kanal geschlossen ist.
main.go: Richte die Pipeline ein und steuere den gleichzeitigen Nachrichtenfluss.Lies die Anzahl der Nachrichten und anschließend die ID und den Inhalt jeder Nachricht ein. Erstelle die Nachrichten und richte zwei Kanäle ein, um deine drei Pipelinestufen zu verbinden. Starte
ProducerundTransformerals Goroutinen und führe anschließendConsumerin der main-Goroutine aus, um die Ergebnisse zu sammeln. Gib jedes Ergebnis in einer eigenen Zeile aus.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Anzahl der Nachrichten (Ganzzahl)
- Folgende Zeilen: Für jede Nachricht zwei Zeilen – die Nachrichten-ID (Ganzzahl), anschließend ihr Inhalt (Zeichenkette)
Beispiel für folgende Eingabe:
2
1
hello world
2
go channelsDeine Ausgabe sollte folgendermaßen aussehen:
Message 1: HELLO WORLD
Message 2: GO CHANNELSDie Pipeline funktioniert folgendermaßen: Producer sendet Nachrichten → Transformer wandelt sie in Großbuchstaben um → Consumer sammelt und formatiert die Ergebnisse. Jeder Pfeil stellt einen Kanal dar, der gleichzeitig ausgeführte Stufen verbindet. Verwende das Paket strings für die Umwandlung in Großbuchstaben.
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Anzahl der Nachrichten lesen
nLine, _ := reader.ReadString('\n')
n, _ := strconv.Atoi(strings.TrimSpace(nLine))
// Nachrichten lesen
messages := make([]Message, n)
for i := 0; i < n; i++ {
idLine, _ := reader.ReadString('\n')
id, _ := strconv.Atoi(strings.TrimSpace(idLine))
contentLine, _ := reader.ReadString('\n')
content := strings.TrimSpace(contentLine)
messages[i] = Message{ID: id, Content: content}
}
// TODO: Erstelle zwei Channels, um die Pipeline-Stufen zu verbinden
// TODO: Starte Producer als Goroutine
// TODO: Starte Transformer als Goroutine
// TODO: Führe Consumer in der main-Goroutine aus und sammle die Ergebnisse
// TODO: Gib jedes Ergebnis in einer eigenen Zeile aus
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP in Go
Externe DateienGo-Workspace & ModulePackages & ImportsExportierte vs. nicht exportierte NamenEinführung in OOP mit GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktorfunktionenRückblick – Einfacher Taschenrechner4Schnittstellen
Einführung in SchnittstellenImplizite ImplementierungSchnittstelle als VertragLeere Schnittstelle (any)TypzusicherungTypwechselZusammensetzung von SchnittstellenStringer- und Error-SchnittstellenRückblick – Formenrechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf PaketebeneGetter- und Setter-MethodenInformationsverbergung in GoRückblick – Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterTypbeschränkungenGenerische StrukturenWorkaround für generische MethodenZusammenfassung – Generische Sammlung2Typen & Structs im Detail
Grundlegende & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct-TagsAnonyme StructsVerschachtelte StructsNullwerte & StandardwerteRückblick – Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen der Struct-EinbettungMethoden-PromotionMehrere Structs einbettenEinbettung vs. AggregationVerbergen eingebetteter MethodenRückblick – Mitarbeiterhierarchie8Fehlerbehandlung & OOP
Das Error-InterfaceBenutzerdefinierte FehlertypenError-Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverRückblick – Dateiparser3Zeiger & Speicher
Grundlagen von Zeigern in GoZeiger auf StructsÜbergabe per Wert vs. ReferenzDie Funktion new()Garbage Collection in GoRückblick – Verkettete Liste erstellen6Polymorphismus in Go
Polymorphismus über InterfacesDuck-Typing in GoRegeln zur Interface-ErfüllungPolymorphe SammlungenDependency InjectionZusammenfassung – Zahlungsprozessor9Konkurrenz & OOP
Grundlagen der GoroutinesChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignRückblick – Worker PoolÜbe selbstständig: Online-Go-Compiler