Assignment Chef icon Assignment Chef
All German tutorials

Programming lesson

Multithreading in C++: Eine praktische Einführung mit 10 Threads

Lerne die Grundlagen des Multithreading in C++ kennen. In diesem Tutorial erstellst du ein Programm mit 10 Threads, das parallele Zahlen generiert – inklusive Join, Race Conditions und Kompilierung mit -pthread.

Multithreading C++ C++ Threads Tutorial std::thread join() C++ Race Conditions C++ Multithreading Beispiel C++ parallele Programmierung Threads spawnen C++ C++ Programmierung lernen Multithreading Übung C++ Thread Sicherheit nice Befehl Multithreading C++ Kompilierung pthread Thread ID C++ Zufallszahlen Threads C++ Projekt Threads

Multithreading in C++: Parallele Programmierung für schnellere Anwendungen

Multithreading ist eine der wirkungsvollsten Techniken, um die Leistung deiner C++-Programme zu steigern. Indem du Aufgaben auf mehrere Threads verteilst, kannst du Rechenarbeit parallelisieren und die Ausführungszeit drastisch verkürzen – besonders auf Mehrkernprozessoren. In diesem Tutorial lernst du, wie du mit std::thread arbeitest, Threads synchronisierst und typische Fallstricke wie Race Conditions vermeidest. Das Beispielprojekt erinnert an ein aktuelles Phänomen: Während der FIFA-WM 2026 versuchen zehn Schiedsrichter gleichzeitig, die genaue Spielzeit zu erfassen – jeder mit einem eigenen Stoppuhr-Thread. Deine Threads suchen parallel nach einer Zufallszahl, ähnlich wie Fans, die gleichzeitig nach einem seltenen Sammelobjekt suchen.

Was ist Multithreading?

Multithreading erlaubt es einem Programm, mehrere Abläufe gleichzeitig auszuführen. Ein Thread ist ein leichtgewichtiger Prozess, der sich den Speicher mit anderen Threads teilt. In C++ nutzt du dafür die Bibliothek <thread>. Der große Vorteil: Zeitaufwändige Operationen wie Datei-I/O, Netzwerkzugriffe oder rechenintensive Schleifen können parallel ablaufen, ohne den Hauptthread zu blockieren. Stell dir vor, du spielst das neue KI-gestützte Spiel „Neural Racer“ – während die KI deine Rennstrategie berechnet, lädt ein anderer Thread die nächste Strecke. Genau so funktioniert echtes Multithreading.

Grundlagen: std::thread und join()

Ein Thread wird mit std::thread t(function, args...) gestartet. Die Funktion läuft dann parallel zum aufrufenden Thread. Wichtig: Du musst t.join() aufrufen, um auf die Beendigung des Threads zu warten. Ohne join() könnte das Hauptprogramm enden, bevor der Thread fertig ist – das führt zu undefiniertem Verhalten. Ein einfaches Beispiel:

#include <iostream>
#include <thread>

void printHello() {
    std::cout << "Hallo aus Thread!\n";
}

int main() {
    std::thread t(printHello);
    t.join();
    return 0;
}

Dieses Programm erzeugt einen Thread, der eine Nachricht ausgibt. Der Hauptthread wartet mit join(), bis der Kindthread beendet ist.

Das Projekt: 10 Threads auf der Suche nach einer Zahl

Deine Aufgabe: Schreibe ein Programm, das eine ganze Zahl als Kommandozeilenargument erhält (z. B. ./threads 1414). Es erzeugt 10 Threads, jeder mit einer eindeutigen ID (0 bis 9). Jeder Thread generiert solange Zufallszahlen zwischen 0 und 9999, bis er die Zielzahl findet. Sobald ein Thread erfolgreich ist, gibt er „Thread [ID] completed.“ aus. Nachdem alle Threads fertig sind, erscheint „All threads have finished finding numbers.“. Das klingt nach einem Wettrennen – ähnlich wie bei der aktuellen TikTok-Challenge „Number Hunt“, bei der zehn Creator parallel nach einer versteckten Zahl suchen. Deine Threads sind die digitalen Jäger.

Schritt 1: Argument einlesen und Funktion definieren

Zuerst liest du das Kommandozeilenargument ein. Da keine Eingabevalidierung nötig ist, kannst du direkt std::atoi oder std::stoi verwenden. Die Thread-Funktion benötigt zwei Parameter: die Thread-ID und das Ziel. So sieht der Code aus:

#include <iostream>
#include <thread>
#include <cstdlib>
#include <ctime>

void searchNumber(int id, int target) {
    int random;
    do {
        random = rand() % 10000;
    } while (random != target);
    std::cout << "Thread " << id << " completed.\n";
}

Beachte: rand() ist nicht threadsicher, aber für dieses Beispiel ausreichend. In einer produktiven Umgebung würdest du <random> mit std::mt19937 verwenden.

Schritt 2: Threads in einer Schleife spawnen

Deine main-Funktion erzeugt die 10 Threads in einer Schleife. Du speicherst sie in einem Array oder Vektor, damit du später auf jeden join() aufrufen kannst. Wichtig: Erzeuge die Threads nicht einzeln, sondern in einer Schleife – das ist Teil der Aufgabenstellung. Ein Vektor von Threads eignet sich perfekt:

int main(int argc, char* argv[]) {
    if (argc != 2) {
        std::cerr << "Usage: " << argv[0] << " <target>\n";
        return 1;
    }
    int target = std::atoi(argv[1]);
    const int NUM_THREADS = 10;
    std::thread threads[NUM_THREADS];
    
    srand(time(nullptr)); // Seed für Zufallszahlen
    
    for (int i = 0; i < NUM_THREADS; ++i) {
        threads[i] = std::thread(searchNumber, i, target);
    }
    
    for (int i = 0; i < NUM_THREADS; ++i) {
        threads[i].join();
    }
    
    std::cout << "All threads have finished finding numbers.\n";
    return 0;
}

Schritt 3: Kompilieren mit -pthread

Um Multithreading in C++ zu nutzen, musst du die pthread-Bibliothek linken. Der Befehl lautet:

g++ threads.cpp -o ex5.out -pthread -std=c++11

Ohne -pthread erhältst du Linker-Fehler. Achte auch auf den C++11-Standard, da std::thread erst seit C++11 verfügbar ist.

Race Conditions und nice

Um zu beobachten, wie Threads in unterschiedlicher Reihenfolge fertig werden, kannst du das Programm mit niedriger Priorität ausführen. Das Tool nice senkt die Priorität, sodass die Threads häufiger unterbrochen werden und sich die Reihenfolge zufälliger ergibt. Beispiel:

nice -n 19 ./ex5.out 1414

Mit nice -n 19 erhält das Programm die niedrigste Priorität. Zusammen mit reduziertem RAM (z. B. 512 MB in der VM) und vielen Kernen entstehen echte Race Conditions – die Threads laufen wild durcheinander. Das ist nicht nur ein netter Effekt, sondern lehrt dich, wie wichtig Synchronisation ist. Stell dir vor, zehn KI-Assistenten wie ChatGPT versuchen gleichzeitig, dasselbe Dokument zu bearbeiten – ohne Koordination entsteht Chaos. In deinem Programm ist das gewollt, aber in echten Anwendungen musst du mit Mutexen oder Semaphoren gegensteuern.

Vollständiger Code (threads.cpp)

#include <iostream>
#include <thread>
#include <cstdlib>
#include <ctime>

void searchNumber(int id, int target) {
    int random;
    do {
        random = rand() % 10000;
    } while (random != target);
    std::cout << "Thread " << id << " completed.\n";
}

int main(int argc, char* argv[]) {
    if (argc != 2) {
        std::cerr << "Usage: " << argv[0] << " <target>\n";
        return 1;
    }
    int target = std::atoi(argv[1]);
    const int NUM_THREADS = 10;
    std::thread threads[NUM_THREADS];
    
    srand(time(nullptr));
    
    for (int i = 0; i < NUM_THREADS; ++i) {
        threads[i] = std::thread(searchNumber, i, target);
    }
    
    for (int i = 0; i < NUM_THREADS; ++i) {
        threads[i].join();
    }
    
    std::cout << "All threads have finished finding numbers.\n";
    return 0;
}

Ausgabe verstehen

Jeder Lauf liefert eine andere Reihenfolge der „completed“-Meldungen. Das liegt daran, dass die Threads nicht deterministisch ablaufen. Die CPU weist ihnen Zeitfenster zu, und je nach Priorität und Auslastung variiert die Reihenfolge. In der Praxis nutzt man das für Lastverteilung oder um rechenintensive Aufgaben zu parallelisieren. Ein aktuelles Beispiel: Während der US-Wahl 2024 analysieren zehn Threads parallel Echtzeitdaten von Wahllokalen – jeder Thread bearbeitet einen Bezirk. Ohne Multithreading würde die Analyse Stunden dauern.

Erweiterungen und nächste Schritte

Du kannst das Programm erweitern, indem du die gefundenen Zahlen zählst oder die Threads mit einem Mutex synchronisierst, damit sie nicht gleichzeitig auf die Konsole schreiben. Auch die Verwendung von std::async oder Thread-Pools ist eine gute Übung. Für dein nächstes Projekt könntest du einen parallelen Sortieralgorithmus oder einen Webserver mit Thread-Pool implementieren. Die Grundlagen, die du hier gelernt hast – Threads erstellen, joinen und Race Conditions verstehen – sind die Basis für jede moderne, performante C++-Anwendung.

Zusammenfassung

In diesem Tutorial hast du gelernt:

  • Wie man mit std::thread arbeitet
  • Warum join() notwendig ist
  • Wie man mehrere Threads in einer Schleife erzeugt
  • Wie man mit nice Race Conditions provoziert
  • Wie man Multithreading-Code mit -pthread kompiliert

Multithreading ist mächtig, aber auch fehleranfällig. Übe mit kleinen Projekten wie diesem, um ein Gefühl für parallele Abläufe zu bekommen. Viel Erfolg beim Experimentieren!