← SEARCHABLE ARCHIVE
C / C++ · 2026-08-14

C++ von den Grundlagen bis zum Profi — Folge 20

Nebenläufigkeit mit Threads, Atomics und Speichermodell beherrschen

INTERACTIVE READING MODE →

Die Artikel in diesem Blog wurden zu Lehrzwecken verfasst. Sie können Fehler enthalten; prüfen Sie die Aussagen daher mit dem Ziel-Compiler und der verwendeten Standardbibliothek.

Greifen mehrere Threads auf denselben Speicher zu, geht es nicht nur um wechselnde Reihenfolgen. Im C++ memory model ist eine unsynchronisierte data race undefined behavior.

Gemeinsame Invarianten ausdrücklich schützen

class Inventory
{
public:
    void Add(Item item)
    {
        std::lock_guard lock(Mutex);
        Items.push_back(std::move(item));
    }

private:
    std::mutex Mutex;
    std::vector<Item> Items;
};

std::lock_guard gibt den mutex am Scope-Ende frei, auch bei Exceptions. Der Lock schützt die gesamte Invariante, nicht nur einen einzelnen vector-Aufruf.

Mehrere mutexes in unterschiedlicher Reihenfolge zu sperren kann deadlock verursachen. Sperren Sie sie mit std::scoped_lock gemeinsam oder definieren Sie eine projektweite Lock-Reihenfolge.

Atomics eignen sich für kleine unabhängige Werte

std::atomic<unsigned int> completedJobs{0};

void MarkCompleted()
{
    completedJobs.fetch_add(1, std::memory_order_relaxed);
}

Für einen Statistikzähler, der keinen anderen Zustand veröffentlicht, kann relaxed ordering genügen. Ein atomic schützt jedoch nicht automatisch die Konsistenz eines Objekts mit mehreren Feldern.

Die voreingestellte sequential consistency ist leichter zu verstehen. Acquire, release und relaxed sollten aus einer bewiesenen happens-before-Beziehung folgen, nicht aus einer Performance-Vermutung.

Thread-Ende mit jthread an die Lebensdauer binden

void Worker(std::stop_token stop)
{
    while (!stop.stop_requested())
        ProcessOneJob();
}

void Run()
{
    std::jthread worker(Worker);
    // Der Destruktor fordert Stop an und führt join aus.
}

C++20 std::jthread fordert beim Zerstören den Stop an und wartet. Ein stop_token ist kooperativ; blockierendes I/O benötigt weiterhin Timeout oder Weckmechanismus.

Entfernen Sie gemeinsamen veränderlichen Zustand, bevor Sie Locks optimieren. Unabhängige Eingaben und Ergebnisse über Nachrichten oder Queues verkleinern die Synchronisationsfläche. Selbst getrennte Atomics können auf derselben Cache-Line false sharing auslösen.

Ein erfolgreicher Test beweist keine Sicherheit. Dokumentieren Sie Besitz, Shutdown, Lock-Reihenfolge und Veröffentlichung und prüfen Sie diese mit ThreadSanitizer und reproduzierbaren Stresstests.