C++ Multithreading: Ein Anfänger-Leitfaden

Hallo daar, zukünftige Programmier-Superstars! Ich bin begeistert, Ihr Guide auf dieser aufregenden Reise in die Welt des C++ Multithreadings zu sein. Als jemand, der seit Jahren Programmierung lehrt, kann ich Ihnen versichern, dass dieses Thema zwar am Anfang vielleicht abschreckend wirken mag, aber tatsächlich ganz faszinierend ist, wenn man es einmal beherrscht. Also, legen wir die Ärmel hoch und tauchen wir ein!

C++ Multithreading

Was ist Multithreading?

Bevor wir in die Details einsteigen, beginnen wir mit den Grundlagen. Stellen Sie sich vor, Sie sind in einer Küche und versuchen, ein komplexes Essen zuzubereiten. Sie könnten alles Schritt für Schritt tun – die Gemüse schneiden, dann die Pasta kochen, dann die Sauce zubereiten. Wäre es nicht effizienter, wenn Sie all diese Aufgaben gleichzeitig erledigen könnten? Das ist essentially, was Multithreading für unsere Programme leistet!

Multithreading ermöglicht es einem Programm, mehrere Aufgaben gleichzeitig auszuführen. Jede dieser Aufgaben wird ein "Thread" genannt. Es ist wie mehrere Köche in der Küche zu haben, jeder für einen anderen Teil des Essens verantwortlich.

Nun, lassen Sie uns erkunden, wie wir diese Kraft in C++ nutzen können!

Threads erstellen

Ein Thread in C++ zu erstellen, ist wie einen neuen Küchenchef zu engagieren. Wir müssen diesem Chef (Thread) sagen, welche Aufgabe er auszuführen hat. In C++ machen wir dies mit der std::thread Klasse aus der <thread> Bibliothek.

Schauen wir uns ein einfaches Beispiel an:

#include <iostream>
#include <thread>

void kochPasta() {
std::cout << "Pasta kocht..." << std::endl;
}

int main() {
std::thread chefEins(kochPasta);
chefEins.join();
return 0;
}

In diesem Beispiel:

  1. Wir inkludieren die notwendigen Bibliotheken: <iostream> für Ein-/Ausgabe und <thread> für Multithreading.
  2. Wir definieren eine Funktion kochPasta(), die unser Thread ausführen soll.
  3. In main() erstellen wir einen Thread namens chefEins und sagen ihm, die kochPasta() Funktion auszuführen.
  4. Wir verwenden join(), um zu warten, bis der Thread seine Aufgabe beendet hat, bevor das Programm endet.

Wenn Sie dieses Programm ausführen, sehen Sie "Pasta kocht..." in der Konsole. Herzlichen Glückwunsch! Sie haben gerade Ihren ersten Thread erstellt!

Threads beenden

Was passiert, wenn unser Küchenchef zu lange braucht, um die Pasta zu kochen? In der Welt der Programmierung müssen wir möglicherweise einen Thread beenden, bevor er seine Aufgabe vollendet hat. Es ist wichtig zu beachten, dass das gewaltsame Beenden von Threads zu Ressourcenlecks und anderen Problemen führen kann. Es ist allgemein besser, Ihre Threads so zu entwerfen, dass sie natürlich enden oder auf Beendigungssignale reagieren.

Hier ist ein Beispiel, wie wir einen Thread so einrichten könnten, dass er auf ein Beendigungssignal reagiert:

#include <iostream>
#include <thread>
#include <atomic>

std::atomic<bool> stop_thread(false);

void kochPasta() {
while (!stop_thread) {
std::cout << "Noch Pasta kocht..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
std::cout << "Pasta kochen gestoppt!" << std::endl;
}

int main() {
std::thread chefEins(kochPasta);

std::this_thread::sleep_for(std::chrono::seconds(5));
stop_thread = true;

chefEins.join();
return 0;
}

In diesem Beispiel:

  1. Wir verwenden eine atomic<bool> Variable stop_thread, um sicher zwischen Threads zu kommunizieren.
  2. Unsere kochPasta() Funktion überprüft nun diese Variable in einer Schleife.
  3. In main() lassen wir den Thread für 5 Sekunden laufen und setzen dann stop_thread auf true.
  4. Der Thread beendet sich durch das Ende seiner Schleife und endet natürlich.

Argumente an Threads übergeben

Was passiert, wenn wir unserem Küchenchef spezifischere Anweisungen geben möchten? In C++ können wir Argumente an unsere Threads übergeben, genau wie wir Argumente an Funktionen übergeben. Lassen Sie uns sehen, wie das geht:

#include <iostream>
#include <thread>
#include <string>

void kochGericht(std::string gericht, int zeit) {
std::cout << "Kocht " << gericht << " für " << zeit << " Minuten." << std::endl;
}

int main() {
std::thread chefEins(kochGericht, "Spaghetti", 10);
std::thread chefZwei(kochGericht, "Pizza", 15);

chefEins.join();
chefZwei.join();

return 0;
}

In diesem Beispiel:

  1. Unsere kochGericht() Funktion nimmt jetzt zwei Parameter: den Namen des Gerichts und die Kochzeit.
  2. Wir erstellen zwei Threads, die jeweils ein anderes Gericht für eine unterschiedliche Zeit kochen.
  3. Wir übergeben diese Argumente direkt beim Erstellen der Threads.

Dies zeigt, wie flexibel Threads sein können - wir können mehrere Threads ähnliche Aufgaben mit verschiedenen Parametern ausführen!

Threads verbinden und lösen

Schließlich sprechen wir über zwei wichtige Konzepte: Threads verbinden und lösen.

Threads verbinden

Wir haben bereits join() in unseren vorherigen Beispielen gesehen. Wenn wir join() auf einen Thread aufrufen, sagen wir unserem Hauptprogramm, dass es auf den Abschluss dieses Threads warten soll, bevor es weitermacht. Es ist wie auf den Abschluss eines Gerichts warten, bevor das Essen serviert wird.

Threads lösen

Manchmal möchten wir vielleicht einen Thread unabhängig laufen lassen, ohne auf seinen Abschluss zu warten. Hier kommt detach() ins Spiel. Ein gelöster Thread läuft weiter im Hintergrund, auch nach dem Ende des Hauptprogramms.

Hier ist ein Beispiel, das beide veranschaulicht:

#include <iostream>
#include <thread>
#include <chrono>

void langsamKochen(std::string gericht) {
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << gericht << " ist fertig!" << std::endl;
}

void schnellKochen(std::string gericht) {
std::cout << gericht << " ist fertig!" << std::endl;
}

int main() {
std::thread langsamChef(langsamKochen, "Eintopf");
std::thread schnellChef(schnellKochen, "Salat");

langsamChef.detach();  // Lassen den langsam kochenden Chef im Hintergrund arbeiten
schnellChef.join();   // Warten, bis der schnelle Chef fertig ist

std::cout << "Hauptprogramm endet. Der langsame Chef könnte noch arbeiten!" << std::endl;
return 0;
}

In diesem Beispiel:

  1. Wir haben zwei Köche: einen, der langsam einen Eintopf kocht, und einen, der schnell einen Salat zubereitet.
  2. Wir lösen den langsam kochenden Chef vom Thread, lassen ihn also im Hintergrund arbeiten.
  3. Wir verbinden den schnellen Chef vom Thread, warten also darauf, dass der Salat fertig ist.
  4. Das Hauptprogramm endet, möglicherweise bevor der Eintopf fertig ist.
Methode Beschreibung Anwendungsfall
join() Wartet auf das Ende des Threads Wenn Sie das Ergebnis des Threads benötigen, bevor Sie fortfahren
detach() Lässt den Thread unabhängig laufen Für Hintergrundaufgaben, die autonom ausgeführt werden können

Und so, meine Freunde! Sie haben gerade Ihre ersten Schritte in die Welt des C++ Multithreadings gemacht. Denken Sie daran, wie das Lernen des Kochens, das Meistern des Multithreadings erfordert Übung. Haben Sie keine Angst, mit diesen Konzepten zu experimentieren, und bald werden Sie komplexe, effiziente Programme wie ein Meisterkoch in der Küche des Codes zubereiten!

Happy coding, und mögeen Ihre Threads immer glatt laufen!

Credits: Image by storyset