C++ Multithreading
Threads sind leichtgewichtige Ausführungseinheiten in einem Programm, die es dem Programm ermöglichen, mehrere Aufgaben gleichzeitig auszuführen.
Multithreading ist eine spezielle Form des Multitaskings, die es einem Computer ermöglicht, zwei oder mehr Programme gleichzeitig auszuführen.
Im Allgemeinen gibt es zwei Arten des Multitaskings:prozessbasiert und threadbasiert。
- Prozessbasiertes Multitasking ist die gleichzeitige Ausführung von Programmen.
- Threadbasiertes Multitasking ist die gleichzeitige Ausführung von Fragmenten desselben Programms.

Multithread-Programmierung in C++ umfasst das Erstellen und Verwalten mehrerer gleichzeitig ausgeführter Threads in einem Programm.
C++ bietet leistungsstarke Multithreading-Unterstützung, insbesondere im C++11-Standard und später, durch<thread>die Standardbibliothek, wodurch Multithread-Programmierung einfacher und sicherer wird.
Konzeptbeschreibung
Thread
- Ein Thread ist ein einzelner sequenzieller Kontrollfluss in der Programmausführung, und mehrere Threads können unabhängig voneinander innerhalb desselben Prozesses ausgeführt werden.
- Threads teilen Ressourcen wie den Adressraum des Prozesses, Dateideskriptoren, Heap und globale Variablen, aber jeder Thread hat seinen eigenen Stack, eigene Register und einen eigenen Programmzähler.
Nebenläufigkeit und Parallelität
- Nebenläufigkeit: Mehrere Aufgaben werden abwechselnd innerhalb von Zeitscheiben ausgeführt, wodurch der Effekt einer gleichzeitigen Ausführung entsteht.
- Parallelität: Mehrere Aufgaben werden gleichzeitig auf mehreren Prozessoren oder Prozessorkernen ausgeführt.
Die Standards C++11 und später bieten Unterstützung für Multithreading, deren Kernkomponenten Folgendes umfassen:
std::thread: Wird zum Erstellen und Verwalten von Threads verwendet.std::mutex: Wird zur gegenseitigen Ausschließung zwischen Threads verwendet und verhindert, dass mehrere Threads gleichzeitig auf gemeinsam genutzte Ressourcen zugreifen.std::lock_guardundstd::unique_lock: Wird zur Verwaltung des Erwerbs und der Freigabe von Sperren verwendet.std::condition_variable: Wird für Bedingungsvariablen zwischen Threads verwendet, um Warten und Benachrichtigung zwischen Threads zu koordinieren.std::futureundstd::promise: Wird zur Implementierung von Wertübergabe und Aufgabensynchronisation zwischen Threads verwendet.
Threads erstellen
Nach C++11 wurde eine neue Standard-Thread-Bibliothek hinzugefügt.std::thread,std::thread in <thread>In der Header-Datei deklariert, sodass bei Verwendung vonstd::threadmuss manin die <thread>Header-Datei einbinden.
Hinweis:Früher verwendeten einige Compiler das Kompilierungsflag für C++11 als-std=c++11:
g++ -std=c++11 test.cpp
std::thread
Das Erstellen eines Threads über einen Funktionszeiger ist die grundlegendste Methode: Verwendungg++ -std=c++11Nach der Kompilierung ist die Ausgabe bei der Ausführung: durch die der Klasse Verwendungg++ -std=c++11Nach der Kompilierung ist die Ausgabe bei der Ausführung: Lambda-Ausdrücke können Inline-Code für die Thread-Ausführung direkt definieren: Verwendungg++ -std=c++11Nach der Kompilierung ist die Ausgabe bei der Ausführung: join() wird verwendet, um zu warten, bis ein Thread die Ausführung beendet hat. Wenn ein Thread-Objekt direkt zerstört wird, ohne join() oder detach() aufzurufen, führt dies zum Absturz des Programms. detach() trennt den Thread vom Hauptthread; der Thread läuft unabhängig im Hintergrund, und der Hauptthread wartet nicht mehr auf ihn. Parameter können per Wert an Threads übergeben werden: Wenn Sie Referenzparameter übergeben müssen, müssen Sie std::ref verwenden: Das Folgende ist ein vollständiges Beispiel, das zeigt, wie die oben genannten drei Methoden verwendet werden, um Threads zu erstellen und Thread-Verwaltung durchzuführen. Compiler-Flag für die Verwendung von C++11-std=c++11: Die Ausgabe des obigen Codes kann sich je nach Plattform oder zwischen Ausführungen unterscheiden, weil die Ausführungsreihenfolge der Threads durch den Scheduling-Algorithmus des Betriebssystems bestimmt wird. Mehrere Threads werden nebenläufig ausgeführt, und die Ausgabe kann sich verzahnen, zum Beispiel: In der Multithread-Programmierung sind Thread-Synchronisation und gegenseitiger Ausschluss zwei sehr wichtige Konzepte. Sie werden verwendet, um den Zugriff mehrerer Threads auf gemeinsam genutzte Ressourcen zu steuern, um Datenrennen, Deadlocks und andere Probleme zu vermeiden. Ein Mutex ist ein Synchronisationsprimitiv, das verhindert, dass mehrere Threads gleichzeitig auf gemeinsam genutzte Ressourcen zugreifen. Wenn ein Thread auf eine gemeinsam genutzte Ressource zugreifen muss, muss er zuerst den Mutex sperren. Wenn der Mutex bereits von einem anderen Thread gesperrt wurde, wird der Thread, der die Sperre anfordert, blockiert, bis der Mutex entsperrt wird. std::mutex: Wird zum Schutz gemeinsam genutzter Ressourcen und zur Vermeidung von Datenrennen verwendet.
std::lock_guardundstd::unique_lock: Verwaltet automatisch das Anfordern und Freigeben von Sperren. Beispiel für die Verwendung eines Mutex: C++ bietet mehrere Sperrtypen, um die Verwendung und Verwaltung von Mutexen zu vereinfachen. Übliche Sperrtypen sind: Beispiel für die Verwendung von Sperren: Bedingungsvariablen werden zur Koordination zwischen Threads verwendet und ermöglichen es einem oder mehreren Threads, auf das Eintreten einer bestimmten Bedingung zu warten. Sie werden typischerweise mit Mutexen verwendet, um die Synchronisation zwischen Threads zu erreichen. std::condition_variableWird verwendet, um Warte- und Benachrichtigungsmechanismen zwischen Threads zu implementieren. Beispiel für die Verwendung von Bedingungsvariablen: Atomare Operationen stellen sicher, dass der Zugriff auf gemeinsam genutzte Daten unteilbar ist, was bedeutet, dass in einer Multithread-Umgebung eine atomare Operation entweder vollständig oder überhaupt nicht ausgeführt wird, ohne Zwischenzustand. Beispiel für die Verwendung atomarer Operationen: Thread-lokaler Speicher ermöglicht es jedem Thread, eine eigene Kopie der Daten zu haben. Dies kann mit dem Schlüsselwort thread_local erreicht werden, wodurch Konkurrenz um gemeinsam genutzte Ressourcen vermieden wird. Beispiel für die Verwendung von thread-lokalem Speicher: Ein Deadlock tritt auf, wenn mehrere Threads darauf warten, dass die jeweils anderen Ressourcen freigeben, aber kein Thread weiter ausgeführt werden kann. Strategien zur Vermeidung von Deadlocks sind: std::future und std::promise: Implementierung der Wertübergabe zwischen Threads. Nachrichtenwarteschlangen (basierend auf std::queue und std::mutex) implementieren eine einfache Inter-Thread-Kommunikation. C++17 führte die Bibliothek für parallele Algorithmen ein ( Weitere Beispiele finden Sie hier: C++-Multithreading:http://www.example.com/w3cnote/cpp-multithread-demo.html C++ std::thread: https://www.example.com/w3cnote/cpp-std-thread.html#include<thread>
std::thread thread_object(callable, args...);
callable: Aufrufbare Objekte, die Funktionszeiger, Funktionsobjekte, Lambda-Ausdrücke usw. sein können.args...: übergeben ancallableDie Parameterliste von.Verwendung von Funktionszeigern
Beispiel
#include <thread>
void printMessage(int count) {
for (int i = 0; i < count; ++i) {
std::cout << "Hello from thread (function pointer)!\n";
}
}
int main() {
std::thread t1(printMessage, 5); // Einen Thread erstellen, wobei ein Funktionszeiger und Parameter übergeben werden
t1.join(); // Warten, bis der Thread abgeschlossen ist
return 0;
}
Hello from thread (function pointer)!
Hello from thread (function pointer)!
Hello from thread (function pointer)!
Hello from thread (function pointer)!
Hello from thread (function pointer)!
Verwendung von Funktionsobjekten
operator()Definiere eine Funktionsobjekt-Methode, um einen Thread zu erstellen:Beispiel
#include <thread>
class PrintTask {
public:
void operator()(int count) const {
for (int i = 0; i < count; ++i) {
std::cout << "Hello from thread (function object)!\n";
}
}
};
int main() {
std::thread t2(PrintTask(), 5); // Einen Thread erstellen, indem ein Funktionsobjekt und Parameter übergeben werden
t2.join(); // Warten, bis der Thread abgeschlossen ist
return 0;
}
Hello from thread (function object)!
Hello from thread (function object)!
Hello from thread (function object)!
Hello from thread (function object)!
Hello from thread (function object)!
Lambda-Ausdrücke verwenden
Beispiel
#include <thread>
int main() {
std::thread t3([](int count) {
for (int i = 0; i < count; ++i) {
std::cout << "Hello from thread (lambda)!\n";
}
}, 5); // Einen Thread erstellen, indem ein Lambda-Ausdruck und Parameter übergeben werden
t3.join(); // Warten, bis der Thread abgeschlossen ist
return 0;
}
Hello from thread (lambda)!
Hello from thread (lambda)!
Hello from thread (lambda)!
Hello from thread (lambda)!
Hello from thread (lambda)!
Thread-Verwaltung
join()
t.join();
detach()
t.detach();
Thread-Parameterübergabe
Übergabe per Wert
std::thread t(func, arg1, arg2);
Übergabe per Referenz
Beispiel
#include <thread>
void increment(int& x) {
++x;
}
int main() {
int num = 0;
std::thread t(increment, std::ref(num)); // Verwenden Sie std::ref, um Referenzen zu übergeben
t.join();
std::cout << "Wert nach Inkrement: " << num << std::endl;
return 0;
}
Umfassendes Beispiel
Beispiel
#include <thread>
using namespace std;
// Eine einfache Funktion, die als Einstiegsfunktion des Threads dient
void foo(int Z) {
for (int i = 0; i < Z; i++) {
cout << "Thread verwendet einen Funktionszeiger als aufrufbaren Parameter\n";
}
}
// Definition der Klasse des aufrufbaren Objekts
class ThreadObj {
public:
void operator()(int x) const {
for (int i = 0; i < x; i++) {
cout << "Thread verwendet ein Funktionsobjekt als aufrufbaren Parameter\n";
}
}
};
int main() {
cout << "Threads 1, 2 und 3 laufen unabhängig voneinander" << endl;
// Einen Thread mit einem Funktionszeiger erstellen
thread th1(foo, 3);
// Thread mit einem Funktionsobjekt erstellen
thread th2(ThreadObj(), 3);
// Thread mit einem Lambda-Ausdruck erstellen
thread th3([](int x) {
for (int i = 0; i < x; i++) {
cout << "Thread verwendet einen Lambda-Ausdruck als aufrufbaren Parameter\n";
}
}, 3);
// Warten, bis alle Threads abgeschlossen sind
th1.join(); // Warten, bis Thread th1 abgeschlossen ist
th2.join(); // Warten, bis Thread th2 abgeschlossen ist
th3.join(); // Warten, bis Thread th3 abgeschlossen ist
return 0;
}
g++ -std=c++11 test.cpp
线程 1 、2 、3 独立运行
线程使用函数指针作为可调用参数
线程使用函数对象作为可调用参数
线程使用 lambda 表达式作为可调用参数
线程使用函数指针作为可调用参数
...
Thread-Synchronisation und gegenseitiger Ausschluss
1. Mutex
std::mutex mtx;
mtx.lock(); // 锁定互斥锁
// 访问共享资源
mtx.unlock(); // 释放互斥锁
std::lock_guard<std::mutex> lock(mtx); // 自动锁定和解锁
// 访问共享资源
Beispiel
std::mutex mtx; // Globaler Mutex
void safeFunction() {
mtx.lock(); // Anforderung, den Mutex zu sperren
// Auf gemeinsam genutzte Ressourcen zugreifen oder sie ändern
mtx.unlock(); // Mutex freigeben
}
int main() {
std::thread t1(safeFunction);
std::thread t2(safeFunction);
t1.join();
t2.join();
return 0;
}
2. Sperren
Beispiel
std::mutex mtx;
void safeFunctionWithLockGuard() {
std::lock_guard<std::mutex> lk(mtx);
// Auf gemeinsam genutzte Ressourcen zugreifen oder sie ändern
}
void safeFunctionWithUniqueLock() {
std::unique_lock<std::mutex> ul(mtx);
// Auf gemeinsam genutzte Ressourcen zugreifen oder sie ändern
// ul.unlock(); // Optional: manuell entsperren
// ...
}
3. Bedingungsvariable
std::condition_variable cv;
std::mutex mtx;
bool ready = false;
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; }); // 等待条件满足
// 条件满足后执行
Beispiel
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void workerThread() {
std::unique_lock<std::mutex> lk(mtx);
cv.wait(lk, []{ return ready; }); // Auf Bedingung warten
// Arbeit ausführen, wenn die Bedingung erfüllt ist
}
void mainThread() {
{
std::lock_guard<std::mutex> lk(mtx);
// Daten vorbereiten
ready = true;
} // Beim Verlassen des Gültigkeitsbereichs entsperren
cv.notify_one(); // Einen wartenden Thread benachrichtigen
}
4. Atomare Operationen
Beispiel
#include <thread>
std::atomic<int> count(0);
void increment() {
count.fetch_add(1, std::memory_order_relaxed);
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
return count; // Sollte 2 zurückgeben
}
5. Thread-lokaler Speicher (TLS)
Beispiel
#include <thread>
thread_local int threadData = 0;
void threadFunction() {
threadData = 42; // Jeder Thread hat seine eigene Kopie von threadData
std::cout << "Thread-Daten: " << threadData << std::endl;
}
int main() {
std::thread t1(threadFunction);
std::thread t2(threadFunction);
t1.join();
t2.join();
return 0;
}
6. Deadlock und Vermeidungsstrategien
Inter-Thread-Kommunikation
Beispiel
std::promise<int> p;
std::future<int> f = p.get_future();
std::thread t([&p] {
p.set_value(10); // Wert setzen, Future auslösen
});
int result = f.get(); // Wert abrufen
<algorithm>), wobei einige Algorithmen die parallele Ausführung unterstützen und Multi-Core-CPUs nutzen, um die Leistung zu verbessern.Beispiel
#include <vector>
#include <execution>
std::vector<int> vec = {1, 2, 3, 4, 5};
std::for_each(std::execution::par, vec.begin(), vec.end(), [](int &n) {
n *= 2;
});
Parallele Algorithmen in der C++-Standardbibliothek
Weitere Erweiterungen