Parallelität und Multithreading in C++: Vollständiger Leitfaden

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Einführung in Nebenläufigkeit und Multithreading in C++



Moderne Anwendungen erfordern maximale Leistung. Nebenläufigkeit und Multithreading in C++ ermöglichen die effiziente Nutzung von Mehrkernprozessoren und beschleunigen die Ausführung von Aufgaben. Ohne das Verständnis dieser Konzepte ist es unmöglich, hochbelastete Systeme, Datenverarbeiter oder Spiele-Engines zu erstellen.



In diesem Artikel werden wir die wichtigsten Mechanismen des Multithreadings in C++ analysieren, angefangen bei den grundlegenden std::thread bis hin zu fortgeschrittenen Mustern. Sie erfahren, wie Sie Datenrennen vermeiden, Threads richtig synchronisieren und sicheren parallelen Code schreiben.



Grundlagen des Multithreadings: std::thread und std::jthread



Die Klasse std::thread (C++11) ist das Fundament der Multithread-Programmierung. Sie ermöglicht es, eine Funktion oder ein callable-Objekt in einem separaten Thread zu starten. Betrachten wir ein einfaches Beispiel:



#include <iostream>#include <thread>

void worker(int id) { std::cout << "Thread " << id << " hat Arbeit begonnen"; std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout << "Thread " << id << " abgeschlossen";}

int main() { std::thread t1(worker, 1); std::thread t2(worker, 2); t1.join(); t2.join(); return 0;}


Wichtige Punkte:

  • join() — der Hauptthread wartet auf die Beendigung des Kind-Threads
  • detach() — der Thread läuft unabhängig weiter (Risiko von Ressourcenlecks)
  • std::jthread (C++20) — ruft automatisch join() im Destruktor auf


Überprüfen Sie immer joinable() vor dem Aufruf von join(), um ein doppeltes Verbinden zu vermeiden.



Thread-Synchronisation: Mutexe und Sperren



Das Hauptproblem des Multithreadings sind Datenrennen, wenn mehrere Threads gleichzeitig eine gemeinsame Variable ändern. Zum Schutz werden Mutexe verwendet.



std::mutex und std::lock_guard



#include <iostream>#include <thread>#include <mutex>

std::mutex mtx;int counter = 0;

void increment() { for (int i = 0; i < 100000; ++i) { std::lock_guard<std::mutex> lock(mtx); ++counter; }}

int main() { std::thread t1(increment); std::thread t2(increment); t1.join(); t2.join(); std::cout << "Ergebnis: " << counter << ""; // 200000 return 0;}


std::lock_guard gibt den Mutex automatisch frei, wenn der Gültigkeitsbereich verlassen wird. Für eine flexiblere Steuerung verwenden Sie std::unique_lock.



Deadlock und std::scoped_lock



Ein Deadlock (Verklemmung) tritt auf, wenn zwei Threads aufeinander warten. Die Lösung ist die Verwendung von std::scoped_lock (C++17), das mehrere Mutexe atomar erfasst:



std::mutex m1, m2;

void safe_function() { std::scoped_lock lock(m1, m2); // sichere Arbeit mit zwei Ressourcen}


Parallele Algorithmen und std::async



Zur Ausführung asynchroner Aufgaben ohne manuelle Thread-Verwaltung verwenden Sie std::async. Es gibt ein std::future zurück, aus dem das Ergebnis abgerufen werden kann.



#include <iostream>#include <future>#include <chrono>

int compute(int n) { std::this_thread::sleep_for(std::chrono::seconds(n)); return n * n;}

int main() { auto future1 = std::async(std::launch::async, compute, 2); auto future2 = std::async(std::launch::async, compute, 3); std::cout << "Ergebnisse: " << future1.get() << ", " << future2.get() << ""; // 4, 9 return 0;}


Startrichtlinien:

  • std::launch::async — Start in einem neuen Thread
  • std::launch::deferred — verzögerte Ausführung im aufrufenden Thread

Blogs

Buchempfehlungen