Nebenläufigkeit, Threads und Datenrennen
Nebenläufigkeit bedeutet, dass mehrere Abläufe in überlappenden Zeiträumen Fortschritt machen. Parallelität bedeutet tatsächliche gleichzeitige Ausführung. Ein Thread ist ein Ausführungsstrang innerhalb eines Prozesses. Ein Prozess ist ein laufendes Programm mit seinen Ressourcen.
Nebenläufigkeit, Threads und Datenrennen#
Dateien, Fehler und Werkzeuge · Lektion 54 von 60
Voraussetzungen: Lambdas, RAII. Lernziel: Du erkennst, wann gleichzeitig verwendete Daten geschützt werden müssen.
Nebenläufigkeit bedeutet, dass mehrere Abläufe in überlappenden Zeiträumen Fortschritt machen. Parallelität bedeutet tatsächliche gleichzeitige Ausführung. Ein Thread ist ein Ausführungsstrang innerhalb eines Prozesses. Ein Prozess ist ein laufendes Programm mit seinen Ressourcen.
Dieses Beispiel benötigt C++20 für std::jthread:
1#include <iostream>2#include <mutex>3#include <thread>4 5int main() {6 int zaehler{};7 std::mutex schutz;8 auto arbeit = [&zaehler, &schutz] {9 for (int i{0}; i < 1000; ++i) {10 std::lock_guard<std::mutex> sperre{schutz};11 ++zaehler;12 }13 };14 {15 std::jthread erster{arbeit};16 std::jthread zweiter{arbeit};17 }18 std::cout << zaehler << '\n';19}Die Ausgabe ist 2000. Eine Mutex ist eine Sperre für gegenseitigen Ausschluss: Nur ein passender Besitzer darf den geschützten Abschnitt gleichzeitig betreten. lock_guard hält die Sperre während seiner Lebensdauer und gibt sie automatisch frei.
Was ohne Sperre falsch wäre#
++zaehler ist nicht automatisch eine unteilbare Operation. Zwei Threads können gleichzeitig auf denselben Wert zugreifen. Ein Datenrennen entsteht bei nicht passend synchronisierten konkurrierenden Zugriffen, wenn mindestens einer schreibt und die Zugriffe nicht ausreichend atomar geregelt sind. Ein solches Rennen ist in C++ undefiniertes Verhalten, nicht nur gelegentlich eine falsche Summe.
Synchronisation stellt vereinbarte Reihenfolgen und Sichtbarkeit zwischen Abläufen her. Jeder beteiligte Zugriff muss dieselbe Schutzregel beachten. Nur den Schreiber zu sperren, während andere ohne Schutz lesen, reicht nicht.
Lebensdauer und Join#
join wartet auf das Ende eines Threads. jthread fordert bei seinem Destruktor kooperatives Stoppen an und wartet anschließend bei Bedarf auf den Thread. Die Arbeit muss eine Stoppanforderung selbst beachten, wenn sie vorzeitig enden soll; unser kurzer endlicher Ablauf läuft regulär fertig.
Bei std::thread aus C++11 muss der Besitzer vor seiner Zerstörung ein noch zuordenbares Threadobjekt ausdrücklich mit join oder einer bewusst sicheren anderen Strategie behandeln. Ein unbedachtes detach kann Lebensdauerprobleme verstecken.
Atomics und Deadlocks#
std::atomic<T> bietet geeignete atomare, also nach ihren Regeln unteilbare Operationen. Es ersetzt nicht automatisch eine gemeinsame Invariante über mehrere Variablen. Ein Deadlock ist gegenseitiges Warten auf Sperren. Vermeide wechselnde Sperrreihenfolgen und unbekannte Funktionsaufrufe während gehaltener Sperren.
Übung#
Warum steht die Ausgabe im Beispiel hinter dem inneren Block?
Lösung
Beim Verlassen werden die jthread-Objekte zerstört und ihre Abläufe abgewartet. Erst danach liest die Ausgabe den fertig berechneten Zähler. Alle referenzierten Daten leben zu diesem Zeitpunkt noch.
Weiterlernen#
Zurück: Projekte mit CMake bauen · Kursübersicht · Weiter: Übungsprojekt: ein geprüfter Taschenrechner
Kommentare 0
Kommentare sind für diese Seite deaktiviert.