Channels & Kommunikation
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 60 von 107.
Channels sind Gos primärer Mechanismus für die Kommunikation zwischen Goroutinen. Anstatt Speicher direkt zu teilen, senden und empfangen Goroutinen Werte über Channels, gemäß der Philosophie von Go: „Kommuniziere nicht durch das Teilen von Speicher; teile Speicher durch Kommunizieren.“
Erstellen Sie einen Channel mit der make-Funktion und dem chan-Schlüsselwort:
messages := make(chan string)Verwenden Sie den Pfeil-Operator <-, 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 den Channel senden
}()
msg := <-messages // vom Channel empfangen
fmt.Println(msg) // Ausgabe: hello
}Standardmäßig blockieren Sendevorgänge und Empfangsvorgänge, bis die andere Seite bereit ist. Wenn die Goroutine "hello" sendet, wartet sie, bis main bereit zum Empfangen ist. Dieses blockierende Verhalten bietet eine integrierte Synchronisation – kein time.Sleep erforderlich.
Sie können einen Channel 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 Strings übertragen. Diese Typsicherheit stellt sicher, dass Goroutinen mit den korrekten Datentypen kommunizieren, wodurch Fehler bereits zur Kompilierzeit statt zur Laufzeit abgefangen werden.
Aufgabe
EinfachLassen Sie uns ein Nachrichten-Relay-System bauen, das demonstriert, wie Goroutinen über Channels kommunizieren. Sie werden eine Pipeline erstellen, in der Nachrichten zwischen verschiedenen Verarbeitungsstufen fließen, wobei jede Stufe gleichzeitig ausgeführt wird.
Sie werden Ihren Code auf zwei Dateien verteilen:
relay.go: Definieren Sie Ihre Nachrichtenverarbeitungs-Pipeline.Erstellen Sie ein
Message-Struct mit den FeldernID(int) undContent(string).Implementieren Sie drei Funktionen, die verschiedene Stufen Ihrer Pipeline darstellen:
Producer(messages []Message, out chan Message)- Nimmt einen Slice von Nachrichten entgegen und sendet jede einzelne an den Output-Channel. Schließen Sie nach dem Senden aller Nachrichten den Channel, um den Abschluss zu signalisieren.Transformer(in chan Message, out chan Message)- Empfängt Nachrichten vom Input-Channel, wandelt den Inhalt jeder Nachricht in Großbuchstaben um und sendet die transformierte Nachricht an den Output-Channel. Wenn der Input-Channel geschlossen wird (erkannt durch die Empfangsform mit zwei Werten), schließen Sie den Output-Channel.Consumer(in chan Message) []string- Empfängt alle Nachrichten vom Input-Channel und sammelt sie in einem Slice von formatierten Strings. Jeder String sollte dem Format folgen:"Message %d: %s"(wobei %d die ID und %s der Content ist). Geben Sie den Slice zurück, wenn der Channel geschlossen wird.
main.go: Richten Sie die Pipeline ein und orchestrieren Sie den gleichzeitigen Nachrichtenfluss.Lesen Sie die Anzahl der Nachrichten ein, und lesen Sie dann die ID und den Inhalt jeder Nachricht. Erstellen Sie die Nachrichten und richten Sie zwei Channels ein, um Ihre drei Pipeline-Stufen zu verbinden. Starten Sie den Producer und den Transformer als Goroutinen und führen Sie dann den Consumer in der Haupt-Goroutine aus, um die Ergebnisse zu sammeln. Geben Sie jedes Ergebnis in einer separaten 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), dann ihr Inhalt (String)
Zum Beispiel, gegeben:
2
1
hello world
2
go channelsIhre Ausgabe sollte sein:
Message 1: HELLO WORLD
Message 2: GO CHANNELSDie Pipeline fließt wie folgt: Producer sendet Nachrichten → Transformer wandelt in Großbuchstaben um → Consumer sammelt und formatiert die Ergebnisse. Jeder Pfeil stellt einen Channel dar, der gleichzeitige Stufen verbindet. Verwenden Sie das strings-Paket für die Umwandlung in Großbuchstaben.
Spickzettel
Channels sind Gos primärer Mechanismus für die Kommunikation zwischen Goroutinen. Erstellen Sie einen Channel mit make und dem Schlüsselwort chan:
messages := make(chan string)Verwenden Sie den Pfeil-Operator <-, um Werte zu senden und zu empfangen. Der Pfeil zeigt in die Richtung des Datenflusses:
messages <- "hello" // an Channel senden
msg := <-messages // von Channel empfangenStandardmäßig blockieren Sende- und Empfangsvorgänge, bis die Gegenseite bereit ist, was eine integrierte Synchronisierung ermöglicht:
func main() {
messages := make(chan string)
go func() {
messages <- "hello" // an Channel senden
}()
msg := <-messages // von Channel empfangen
fmt.Println(msg) // Ausgabe: hello
}Schließen Sie einen Channel, um zu signalisieren, dass keine weiteren Werte gesendet werden. Empfänger können das Schließen mithilfe der Form des Empfangs mit zwei Werten erkennen:
close(messages)
msg, ok := <-messages
if !ok {
fmt.Println("Channel closed")
}Channels sind typisiert – ein chan string kann nur Strings übertragen, was Typsicherheit zur Kompilierzeit bietet.
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: Zwei Kanäle erstellen, um die Pipeline-Stufen zu verbinden
// TODO: Producer als Goroutine starten
// TODO: Transformer als Goroutine starten
// TODO: Consumer in der Haupt-Goroutine ausführen und Ergebnisse sammeln
// TODO: Jedes Ergebnis in einer separaten Zeile ausgeben
}
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 Go OOP
Externe DateienGo Workspace & ModulePackages & ImportsExportierte vs. nicht-exportierte NamenEinführung in OOP in GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktor-FunktionenZusammenfassung – Einfacher Taschenrechner4Interfaces
Einführung in InterfacesImplizite ImplementierungInterface als VertragLeeres Interface (any)Type AssertionType SwitchInterface-KompositionStringer & Error InterfacesRückblick - Formen-Rechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf Paket-EbeneGetter- & Setter-MethodenInformation Hiding in GoZusammenfassung - Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterType ConstraintsGenerische StructsWorkaround für generische MethodenZusammenfassung – Generische Collection2Vertiefung: Typen & Structs
Basis- & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct TagsAnonyme StructsVerschachtelte StructsZero Values & StandardwerteZusammenfassung - Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen des Struct EmbeddingMethod PromotionEinbetten mehrerer StructsEmbedding vs. AggregationShadowing eingebetteter MethodenZusammenfassung – Mitarbeiter-Hierarchie8Fehlerbehandlung & OOP
Das error-InterfaceEigene FehlertypenError Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverZusammenfassung – Datei-Parser3Zeiger & Speicher
Zeiger-Grundlagen in GoZeiger auf StructsWertübergabe vs. ReferenzübergabeDie new() FunktionGarbage Collection in GoWiederholung – Linked List Builder6Polymorphismus in Go
Polymorphismus über InterfacesDuck Typing in GoInterface-ErfüllungsregelnPolymorphe CollectionsDependency InjectionZusammenfassung – Payment Processor9Nebenläufigkeit & OOP
Goroutines GrundlagenChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignZusammenfassung - Worker Pool