Zum Inhalt springen

Informatik · Klasse 12 · aktualisiert

Erzeuger-Verbraucher-Problem einfach erklärt

Du lernst, wie Erzeuger und Verbraucher einen Puffer teilen, was mutex, sem_read und sem_write tun und warum Java-Threads erneut prüfen.

0 von 13 Aufgaben gelöst

Erzeuger-Verbraucher-Problem einfach erklärt

Kurz gesagt

Beim Erzeuger-Verbraucher-Problem legen Erzeuger Elemente in einen gemeinsamen Puffer, Verbraucher entnehmen sie. Der Puffer braucht Schutz, ein Erzeuger wartet bei vollem, ein Verbraucher bei leerem Puffer. Das regeln in den meisten Lösungen die Semaphore mutex, sem_read und sem_write.

Stell dir eine Bäckerei vor: Einer backt Brötchen und legt sie in einen Korb, ein anderer verkauft sie. Ist der Korb voll, kann der Bäcker nichts mehr ablegen. Ist er leer, hat der Verkäufer nichts zu tun. Genau dieses Muster steckt in vielen Programmen, in denen mehrere Threads zusammenarbeiten.

Deine Lernziele

Hake ab, was du schon kannst. Löst du alle Aufgaben eines Abschnitts, hakt Mela das Ziel für dich ab.

4 Abschnitte

Erzeuger legen Elemente in eine gemeinsame Datenstruktur, den Puffer. Verbraucher entnehmen Elemente und verarbeiten sie. Bei beschränkter Kapazität kann der Puffer zum Beispiel ein Ringpuffer sein. Es gibt mindestens einen Erzeuger und mindestens einen Verbraucher.

Der Zugriff auf den Puffer ist ein kritischer Abschnitt. Legt ein Erzeuger gerade ein Element ab oder entfernt ein Verbraucher gerade eines, darf kein anderer Prozess diesen Vorgang unterbrechen und ebenfalls den Puffer ändern. Sonst kann der Puffer in einen inkonsistenten Zustand geraten. Das zeigt schon ein einfacher Zähler: Wenn zwei Threads zeitlich verschränkt arbeiten und beide denselben alten Wert lesen, geht ein Erhöhen verloren. Dabei nehmen wir an, dass das Erhöhen nicht in einem einzigen unteilbaren Schritt möglich ist.

Zusätzlich müssen Prozesse manchmal warten. Ist der Puffer voll, darf ein Erzeuger nichts ablegen. Ist er leer, kann ein Verbraucher nichts entnehmen. Solche Prozesse werden blockiert und wieder aufgeweckt, wenn sich der Zustand des Puffers geändert hat. Ein Vergleich aus der Fabrik: Ist das Zwischenlager voll, muss die vorgelagerte Produktionsstation stoppen.

Definition

Kritischer Abschnitt

Ein kritischer Abschnitt ist eine Ansammlung von Programmanweisungen, in der sich zu einer Zeit nur ein einziger Prozess oder Thread aufhalten darf.

Beispiel: Zwei Threads erhöhen einen Zähler ohne Schutz

  1. 1Thread X liest den Wert von zaehler.
  2. 2Thread Y liest den Wert von zaehler und bekommt denselben alten Wert.
  3. 3Beide erhöhen ihre private Kopie um 1.
  4. 4Beide schreiben ihre Kopie zurück, Y schreibt als letzter.
  5. 5Am Ende fehlt ein Erhöhen: Das Ergebnis ist falsch.

Merke: Zugriffe auf den Puffer brauchen Schutz. Ein Erzeuger wartet bei vollem Puffer, ein Verbraucher bei leerem Puffer.

Teste dich

LeichtWarum muss ein Verbraucher bei leerem Puffer warten?
MittelWas geht beim ungeschützten Zähler schief?

Alles auf einen Blick

Erzeuger-Verbraucher-Problem

  • Schutz

    Zugriffe auf den Puffer sind ein kritischer Abschnitt, nur ein Prozess darf gleichzeitig ändern.

  • Warten

    Der Erzeuger wartet bei vollem, der Verbraucher bei leerem Puffer.

  • Semaphore

    mutex schützt, sem_read zählt Elemente, sem_write zählt freie Plätze.

  • Reihenfolge

    Erst P auf sem_write oder sem_read, dann P(mutex), ändern, V(mutex), dann V beim anderen Semaphor.

  • Java

    Nach wait() muss der Thread seine Bedingung erneut prüfen.

Musteraufgabe · Schritt für Schritt

Verbraucher kommt zuerst: Ablauf mit N = 4 (ausgedachtes Beispiel)

  1. 1Start: mutex = 1, sem_read = 0, sem_write = 4, der Puffer ist leer.
  2. 2Ein Verbraucher ruft P(sem_read) auf. sem_read ist 0, also wartet er.
  3. 3Ein Erzeuger ruft P(sem_write) auf: sem_write sinkt von 4 auf 3.
  4. 4Er ruft P(mutex) auf: mutex sinkt von 1 auf 0. Jetzt schreibt er sein Element.
  5. 5Er ruft V(mutex) auf: mutex steigt wieder auf 1. Dann ruft er V(sem_read) auf und weckt den Verbraucher.
  6. 6Der Verbraucher ruft P(mutex) auf, liest das Element und ruft V(mutex) auf.
  7. 7Zuletzt ruft er V(sem_write) auf: sem_write steigt von 3 auf 4. Alle 4 Plätze sind wieder frei.

Fehler finden

Prüfe die Aussagen zu den Semaphoren In einer Zeile steckt ein Fehler. Tippe sie an.

Lückentext

Wähl in jeder Lücke das passende Wort und prüf dann deine Antworten.

Der Semaphor sem_read zählt die Elemente im Puffer und startet bei leerem Puffer mit . Der Erzeuger ruft auf, bevor er in den Puffer schreibt. Ein aufgeweckter Java-Thread prüft seine Bedingung , weil Java keine Bedingungsvariablen vorsieht.

Karteikasten

Erst selbst überlegen, dann umdrehen.

Übung mit Feedback

Abschluss-Check: Erzeuger-Verbraucher-Problem

LeichtBei einem Puffer mit begrenzter Größe: Wann muss ein Erzeuger warten?
MittelWas bedeutet der Startwert N bei sem_write?
SchwerAusgedachtes Beispiel: Zwei Verbraucher warten mit wait(). Ein Erzeuger legt ein Element ab und ruft notifyAll() auf. Der erste Verbraucher nimmt das Element. Was muss der zweite tun, wenn er aufwacht?

Weiterlesen in Informatik