Der Umgang mit Threads in C++ ist schnell erzählt. Durch den Aufruf std::thread wird ein neuer Thread erzeugt. Dieser Thread benötigt ein Arbeitspaket und startet sofort. Der erzeugende Thread (Der Erzeuger) muss sich um die Lebenszeit seines erzeugten Threads (Das Kind) kümmern. Der Erzeuger wartet entweder, bis sein Kind seine Arbeit vollzogen hat oder er trennt sich von ihm. Die Daten nimmt das Kind per Copy oder Referenz an.
Nach diesem Schnelldurchlauf folgen nun die einzelnen Punkte im Detail.
Einen Thread erzeugen und ausführen
Nun ein bisschen formaler. Ein Thread als ausführbare Einheit (thread of execution) enthält seine aufrufbare Einheit und startet diese sofort.
Dieser Satz benötigt ein paar Erläuterungen:
- Eine aufrufbare Einheit ist alles, was sich wie eine Funktion verhält. Das ist typischerweise eine Funktion, ein Funktionsobjekt oder eine Lambda-Funktion.
- Ein Funktionsobjekt ist eine Instanz einer Klasse, für die der Klammeroperator überladen wurde. Der wesentliche Unterschied von Funktionen zu Funktionsobjekten ist, das Funktionsobjekte Zustand besitzen können.
-
Eine Lambda-Funktion (anonymous function) ist ein nackter Funktionskörper, der keinen Namen besitzt. Sie kann direkt dort verwendet werden, wo sie benötigt wird. Schließen Lambda-Funktionen ihren Aufrufkontext ein, werden sie gerne Closure genannt.
Nach der Theorie ein kleines Beispiel.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
#include <iostream>
#include <thread>
void helloFunction(){
std::cout << "Hello C++11 from a function." << std::endl;
}
class HelloFunctionObject {
public:
void operator()() const {
std::cout << "Hello C++11 from a function object." << std::endl;
}
};
int main(){
std::cout << std::endl;
// thread executing helloFunction
std::thread t1(helloFunction);
// thread executing helloFunctionObject
HelloFunctionObject helloFunctionObject;
std::thread t2(helloFunctionObject);
// thread executing lambda function
std::thread t3([]{std::cout << "Hello C++11 from lambda function." << std::endl;});
// ensure that t1, t2 and t3 have finished before the main thread terminates
t1.join();
t2.join();
t3.join();
std::cout << std::endl;
};
|
Sowohl Thread t1 als auch Thread t2 und t3 geben ihre Nachricht auf der Konsole aus. Das Arbeitspaket des Threads t2 ist ein Funktionsobjekt (Zeile 8 - 13), das des Threads t3 eine Lambda-Funktion (Zeile 27). Zuletzt wartet der Erzeugerthread oder auch der main-Thread in den Zeilen 30 - 32 darauf, bis seine Kinder fertig sind.
Interessanter ist da schon die Ausgabe des Programms.
Die zwei Programmausführungen unterscheiden sich in zweifacher Hinsicht. Zum einen werden die Kinderthreads in den zwei Durchläufen in verschiedenen Reihenfolgen ausgeführt, zum andern ist die Ausgabe der Threads verschränkt. So wird im zweiten Durchlauf der Zeilenumbruch der Funktion helloFunktion erst nach der Lambda-Funktion ausgegeben.
Weiter geht's im nächsten Artikel mit der Lebenszeit von Threads.
Hintergrundwissen
- Aufrufbare Einheiten und Lambda-Funktionen
-
Die Details rund um aufrufbare Einheiten und insbesondere Lambda-Funktionen sind in meinen Linux-Magazin Artikel 1 und 2 erklärt. Zu einfachsten sind diese unter http://www.grimm-jaud.de/index.php/modernes-c-in-der-praxis-linux-magazin-a zu finden.
Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library". Hole dir dein E-Book. Unterstütze meinen Blog.
Weiterlesen...