Menu
Coddy logo textTech

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.

challenge icon

Aufgabe

Einfach

Lassen 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 Feldern ID (int) und Value (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 mit len(out) im Format Buffer has [count] items aus. 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
cherry

Ihre 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 - cherry

Beachten 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 befinden
  • cap(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
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung