Gepufferte vs. ungepufferte Channels
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 61 von 107.
Die Kanäle, die wir bisher verwendet haben, sind ungepuffert – sie haben keine Kapazität, um Werte zu speichern. Eine Sendeoperation blockiert, bis eine andere Goroutine empfängt, und umgekehrt. Gepufferte Kanäle fügen internen Speicher hinzu, was es ermöglicht, Sendevorgänge ohne einen sofortigen Empfänger abzuschließen.
Erstellen Sie einen gepufferten Channel, indem Sie eine Kapazität an make übergeben:
// Ungepuffert - Kapazität 0
unbuffered := make(chan int)
// Gepuffert - Kapazität 3
buffered := make(chan int, 3)Bei einem gepufferten Kanal blockieren Sendevorgänge nur, wenn der Puffer voll ist, und Empfangsvorgänge blockieren nur, wenn der Puffer leer ist:
ch := make(chan string, 2)
ch <- "first" // blockiert nicht - Puffer hat Platz
ch <- "second" // blockiert nicht - Puffer hat Platz
// ch <- "third" würde blockieren - Puffer ist voll
fmt.Println(<-ch) // "first"
fmt.Println(<-ch) // "second"Verwenden Sie len(ch), um zu prüfen, wie viele Elemente sich derzeit im Puffer befinden, und cap(ch) für die Gesamtkapazität.
Wann man welche verwendet: Ungepufferte Channels bieten eine enge Synchronisation – der Sender weiß, dass der Empfänger den Wert erhalten hat. Gepufferte Channels entkoppeln das Timing von Sender und Empfänger, was nützlich ist, wenn Producer und Consumer mit unterschiedlichen Geschwindigkeiten arbeiten. Gepufferte Channels können jedoch Synchronisationsfehler verschleiern, weshalb ungepufferte Channels bevorzugt werden sollten, sofern kein spezifischer Grund für eine Pufferung vorliegt.
Aufgabe
EinfachLassen Sie uns einen Batch-Prozessor bauen, der den Unterschied zwischen gepufferten und ungepufferten Channels demonstriert. Sie erstellen ein System, bei dem ein Producer Elemente an einen Prozessor sendet und dabei gepufferte Channels verwendet, um deren zeitlichen Ablauf zu entkoppeln und dem Producer zu ermöglichen, im Voraus zu arbeiten.
Sie werden Ihren Code in zwei Dateien organisieren:
processor.go: Definieren Sie Ihre Batch-Verarbeitungslogik mithilfe von Channels.Erstellen Sie ein
Item-Struct mit den FeldernID(int) undValue(string).Implementieren Sie zwei Funktionen:
Producer(items []Item, out chan Item)- Sendet jedes Element an den Output-Channel. Geben Sie nach dem Senden jedes Elements Folgendes aus:Produced item [ID]. Nachdem alle Elemente gesendet wurden, geben Sie die aktuelle Anzahl der im Puffer wartenden Elemente mitlen(out)im FormatBuffer has [count] itemsaus. Schließen Sie dann den Channel.Consumer(in chan Item) []string- Empfängt Elemente aus dem Input-Channel und sammelt sie in einem Slice aus formatierten Strings. Für jedes empfangene Element sollte der String wie folgt lauten:Consumed: [ID] - [Value]. Geben Sie den Slice zurück, wenn der Channel geschlossen wird.
main.go: Richten Sie den gepufferten Channel ein und koordinieren Sie Producer und Consumer.Lesen Sie die Pufferkapazität ein, dann die Anzahl der Elemente, gefolgt von der ID und dem Wert jedes Elements. Erstellen Sie einen gepufferten Channel mit der angegebenen Kapazität. Führen Sie zuerst den Producer aus (nicht als Goroutine), um den Puffer zu füllen, und führen Sie dann den Consumer aus, um alle Elemente zu verarbeiten. Geben Sie jedes konsumierte Ergebnis in einer separaten Zeile aus.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Pufferkapazität (Ganzzahl)
- Zeile 2: Anzahl der Elemente (Ganzzahl)
- Folgende Zeilen: Für jedes Element zwei Zeilen - die Element-ID (Ganzzahl), dann sein Wert (String)
Zum Beispiel, gegeben:
3
3
1
apple
2
banana
3
cherryIhre Ausgabe sollte wie folgt aussehen:
Produced item 1
Produced item 2
Produced item 3
Buffer has 3 items
Consumed: 1 - apple
Consumed: 2 - banana
Consumed: 3 - cherryBeachten Sie, dass der Producer bei einer Pufferkapazität von 3 alle 3 Elemente senden kann, ohne zu blockieren, und der Puffer 3 wartende Elemente anzeigt, bevor der Consumer startet. Wäre der Puffer kleiner als die Anzahl der Elemente, würde der Producer blockieren und auf freien Platz warten.
Spickzettel
Channels in Go können ungepuffert (Kapazität 0) oder gepuffert (mit internem Speicher) sein.
Erstellen Sie Channels mit make:
// Ungepuffert - Kapazität 0
unbuffered := make(chan int)
// Gepuffert - Kapazität 3
buffered := make(chan int, 3)Verhalten von gepufferten Channels:
- Sendevorgänge blockieren nur, wenn der Puffer voll ist
- Empfangsvorgänge blockieren nur, wenn der Puffer leer ist
ch := make(chan string, 2)
ch <- "first" // blockiert nicht - Puffer hat Platz
ch <- "second" // blockiert nicht - Puffer hat Platz
// ch <- "third" würde blockieren - Puffer ist voll
fmt.Println(<-ch) // "first"
fmt.Println(<-ch) // "second"Pufferstatus prüfen:
len(ch)- Anzahl der Elemente, die sich derzeit im Puffer befindencap(ch)- Gesamte Pufferkapazität
Wann man sie verwendet:
- Ungepuffert: Starke Synchronisation – der Sender weiß, dass der Empfänger den Wert erhalten hat
- Gepuffert: Entkopplung des Timings von Sender und Empfänger, wenn diese mit unterschiedlichen Geschwindigkeiten arbeiten
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Pufferkapazität lesen
line, _ := reader.ReadString('\n')
bufferCapacity, _ := strconv.Atoi(strings.TrimSpace(line))
// Anzahl der Elemente lesen
line, _ = reader.ReadString('\n')
numItems, _ := strconv.Atoi(strings.TrimSpace(line))
// ID und Wert jedes Elements lesen
items := make([]Item, numItems)
for i := 0; i < numItems; i++ {
line, _ = reader.ReadString('\n')
id, _ := strconv.Atoi(strings.TrimSpace(line))
line, _ = reader.ReadString('\n')
value := strings.TrimSpace(line)
items[i] = Item{ID: id, Value: value}
}
// TODO: Erstelle einen gepufferten Channel mit der angegebenen Kapazität
// TODO: Führe den Producer aus (nicht als Goroutine), um den Puffer zu füllen
// TODO: Führe den Consumer aus, um alle Elemente zu verarbeiten
// TODO: Gib jedes verarbeitete Ergebnis in einer separaten 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 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