Menu
Coddy logo textTech

Gepufferte vs. ungepufferte Channels

Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 61 von 107.

Die bisher von uns verwendeten Kanäle 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 einen internen Speicher hinzu, sodass Sendevorgänge ohne einen sofortigen Empfänger abgeschlossen werden können.

Erstelle einen gepufferten Kanal, indem du eine Kapazität an make übergibst:

// 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 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"

Verwende len(ch), um zu überprüfen, wie viele Elemente sich derzeit im Puffer befinden, und cap(ch) für die Gesamtkapazität.

Wann welches verwendet wird: Ungepufferte Kanäle ermöglichen eine enge Synchronisierung: Der Sender weiß, dass der Empfänger den Wert erhalten hat. Gepufferte Kanäle entkoppeln den zeitlichen Ablauf von Sender und Empfänger und sind nützlich, wenn Produzenten und Konsumenten mit unterschiedlicher Geschwindigkeit arbeiten. Gepufferte Kanäle können jedoch Synchronisierungsfehler verschleiern. Verwende daher bevorzugt ungepufferte Kanäle, es sei denn, du hast einen bestimmten Grund für eine Pufferung.

challenge icon

Aufgabe

Einfach

Erstellen wir einen Batch-Prozessor, der den Unterschied zwischen gepufferten und ungepufferten Kanälen demonstriert. Du erstellst ein System, in dem ein Produzent Elemente an einen Prozessor sendet, wobei gepufferte Kanäle verwendet werden, um ihre zeitliche Abstimmung zu entkoppeln und dem Produzenten zu ermöglichen, vorauszuarbeiten.

Du organisierst deinen Code über zwei Dateien:

  • processor.go: Definiere deine Batch-Verarbeitungslogik mithilfe von Kanälen.

    Erstelle eine Item-Struktur mit den Feldern ID (int) und Value (string).

    Implementiere zwei Funktionen:

    • Producer(items []Item, out chan Item) – Sendet jedes Element an den Ausgabekanal. Gib nach dem Senden jedes Elements Produced item [ID] aus. Nachdem alle Elemente gesendet wurden, gib die aktuelle Anzahl der im Puffer wartenden Elemente mithilfe von len(out) im Format Buffer has [count] items aus. Schließe anschließend den Kanal.
    • Consumer(in chan Item) []string – Empfängt Elemente vom Eingangskanal und sammelt sie in einem Slice formatierter Zeichenketten. Für jedes empfangene Element soll die Zeichenkette wie folgt aussehen: Consumed: [ID] - [Value]. Gib den Slice zurück, sobald der Kanal geschlossen ist.
  • main.go: Richte den gepufferten Kanal ein und koordiniere Produzent und Konsument.

    Lies zuerst die Pufferkapazität, dann die Anzahl der Elemente und anschließend die ID und den Wert jedes Elements ein. Erstelle einen gepufferten Kanal mit der angegebenen Kapazität. Führe zuerst den Producer aus (nicht als Goroutine), um den Puffer zu füllen, und führe anschließend den Consumer aus, um alle Elemente zu verarbeiten. Gib 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 (Zeichenkette)

Zum Beispiel bei folgenden Eingaben:

3
3
1
apple
2
banana
3
cherry

Deine 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

Beachte, dass der Produzent bei einer Pufferkapazität von 3 alle 3 Elemente senden kann, ohne zu blockieren, und der Puffer 3 wartende Elemente anzeigt, bevor der Konsument startet. Wenn der Puffer kleiner als die Anzahl der Elemente wäre, würde der Produzent auf freien Platz warten und blockieren.

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 konsumierte 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

Übe selbstständig: Online-Go-Compiler