Threads, Semaphore, Mutex und Counting Semaphore — anschaulich erklärt mit Analogien aus dem Alltag, animierten Visualisierungen, erklärtem C++-Code, einem Fachwortglossar (Englisch → Deutsch) und einem selbstkorrigierenden Quiz. Alles, um die Konzepte wirklich zu verstehen und zu behalten.
Bevor es in die Konzepte geht: die wichtigsten Grundlagen aus den ersten Terminen kompakt. Klapp die Abschnitte auf, um dein Wissen aufzufrischen.
Dieselben Konzepte gibt es einmal als native FreeRTOS-Funktion und einmal als portable Pigweed-Abstraktion (pw_*). Die Tabelle zeigt beide nebeneinander.
Teil 1 behandelt: Threads/Tasks · Binäre Semaphore · Mutex · Counting Semaphore
Ein Thread fragt die Tastatur ab (T1), ein zweiter berechnet die Physik der Welt (T2), ein dritter lädt im Hintergrund neue Texturen von der Festplatte (T3). Ohne Threads würde das Spiel einfrieren, sobald man ein neues Level betritt — weil eine einzige Aufgabe alles blockieren würde.
while(true) — die Endlosschleife macht daraus eine dauerhaft laufende Task (LED blinkt regelmäßig).sleep_for(...) versetzt die Task in den blockierten Zustand — in dieser Zeit belegt sie keine CPU, andere Tasks laufen.main() mit DetachedThread(options, ...) — Name, Priorität und Stack-Kontext werden über ThreadOptions gesetzt.Die Läufer (Tasks) warten blockiert an der Linie. Sobald der Schiedsrichter die Pistole abfeuert (Signal geben), dürfen sie losrennen. Es geht nicht um den Schutz einer Ressource, sondern um das Warten auf ein Ereignis. Ein Producer signalisiert, ein Consumer wartet.
release() (give) erhöht die Semaphore auf 1 — das ist der „Startschuss". Läuft periodisch.acquire() (take) blockiert bei 0 und läuft erst weiter, wenn der Producer signalisiert — keine CPU-Last beim Warten.Es gibt nur einen Schlüssel. Wer ihn hat, darf rein — alle anderen warten vor der Tür. Erst wenn der Vorgänger den Schlüssel zurückgibt, kann der Nächste die Ressource (Toilette) nutzen. Das verhindert, dass zwei Leute gleichzeitig denselben Raum nutzen und Chaos entsteht. Besitzprinzip: nur wer sperrt, gibt frei.
std::lock_guard lock(mutex) sperrt beim Betreten des Blocks. Task_2 muss warten, bis Task_1 fertig ist.{ }-Klammern wird der Mutex automatisch freigegeben — auch bei einem Fehler. Kein vergessenes unlock().% 201 begrenzt den Zähler zyklisch: er läuft im Kreis 0…200 und überschreitet nie 200.mutex und shared_counter. Ohne Mutex käme es beim gleichzeitigen counter+1 zu einer Race Condition — mit Mutex zählt der geteilte Zähler sauber hoch.Jeder Student, der ein Rad nimmt, verringert den Zähler. Sind alle 10 Räder weg (Zähler = 0), muss der Nächste warten. Sobald jemand ein Rad zurückbringt, wird der Zähler erhöht und die nächste wartende Person darf losfahren. Die Semaphore verwaltet also einen Pool identischer Ressourcen.
kSlots = 8.std::atomic<int> schützt den Anzeige-Zähler vor Race Conditions. fetch_sub(1) zieht 1 ab, liefert aber den alten Wert zurück — daher -1 für den aktuellen.Die drei Synchronisationsmittel im direkten Vergleich — Zustände, Grundkonzept und Einsatzzweck.
Um das zeitliche Verhalten der Tasks sichtbar zu machen, zeichnet der Simulator Ereignisse zur Laufzeit auf. So erkennt man genau, wann ein Task auf einen Mutex oder eine Semaphore warten muss.
tasks_trace schreibt Systemereignisse per Knopfdruck in eine Binärdatei.decode_trace.py erzeugt mit der ELF-Datei eine JSON-Datei.ui.perfetto.dev: Task-Wechsel, CPU-Last, Sleep-Phasen & Signalzustände auf der Zeitachse.Jeder Fachbegriff mit englischer Bezeichnung, deutscher Übersetzung und einer verständlichen Erklärung. Filtere nach Kategorie oder such nach einem Stichwort.
Wähle bei jeder Frage eine Antwort und klicke „Antwort prüfen". Du bekommst sofort richtig/falsch mit einer Erklärung.
Schreibe zuerst deine eigene Antwort ins Feld — dann klapp die Musterlösung auf und vergleiche. So merkst du dir die Konzepte am besten.
counter++.counter++ ist auf CPU-Ebene drei Schritte: (1) Wert aus dem RAM in ein Register laden, (2) im Register um 1 erhöhen, (3) zurück in den RAM schreiben. Unterbricht ein zweiter Thread zwischen diesen Schritten, arbeiten beide mit dem alten Wert — eine Erhöhung geht verloren. Abhilfe: std::atomic oder ein Mutex.std::lock_guard?std::lock_guard sperrt den Mutex im Konstruktor und gibt ihn beim Verlassen des Gültigkeitsbereichs im Destruktor automatisch wieder frei — selbst bei einem vorzeitigen return oder einer Exception. So kann man das Entsperren nicht vergessen.tasks_trace schreibt Systemereignisse zur Laufzeit per Knopfdruck in eine Binärdatei. 2) Decodieren: decode_trace.py wandelt die Rohdaten mithilfe der kompilierten ELF-Datei in JSON um. 3) Analysieren: Die JSON-Datei wird in Perfetto (ui.perfetto.dev) geladen — dort sieht man Task-Wechsel, CPU-Auslastung, Sleep-Phasen und Signalzustände auf einer Zeitachse.acquire() nimmt eine Ressource und verringert den Zähler; bei 0 muss gewartet werden. Jeder release() gibt eine zurück und erhöht den Zähler. So begrenzt die Semaphore die Zahl gleichzeitiger Nutzer.