Der flüchtige Qualifier in C++
In diesem Artikel wird der Qualifier volatile
in C++ vorgestellt.
Verwenden Sie den Qualifier volatile
, um das Objekt zu kennzeichnen, das durch einen anderen Thread oder eine externe Aktion in C++ geändert wird
Das Verhalten des Schlüsselworts volatile
sollte im Allgemeinen als hardwareabhängig angesehen werden, und Entwickler von User-Space-Anwendungen sollten immer die spezifischen Compiler-Handbücher konsultieren, um zu erfahren, wie der Qualifizierer in verschiedenen Szenarien interpretiert werden kann. Normalerweise teilt das Schlüsselwort volatile
dem Compiler mit, dass das angegebene Objekt nicht für Ladeoperationen optimiert und immer aus dem Hauptspeicher statt aus den Registern oder Caches abgerufen werden soll. Beachten Sie, dass es mehrere Hierarchien von Caches gibt, auf die die Software meistens keinen Zugriff hat und die ausschließlich in der Hardware verwaltet werden. Wenn der Compiler jedoch versucht, den Speicherort in das Register zu laden, wird er automatisch zwischengespeichert. Somit kann ein konsequenter Zugriff auf dieselbe Speicherstelle von den Cache-Zeilen in der Nähe der CPU und um ein Vielfaches schneller als beim RAM erfolgen.
Wenn das Objekt durch ein externes Signal oder eine Unterbrechungs-ähnliche Routine modifiziert wird, sollte auf den geänderten Wert aus dem RAM zugegriffen werden, da der zwischengespeicherte Wert nicht mehr gültig ist. So werden Zugriffe auf volatile
Objekte vom Compiler entsprechend gehandhabt. Um das mögliche Szenario zu demonstrieren, implementieren wir eine Funktion, die die globale Variable volatile
Integer modifiziert, und eine weitere Funktion, die dieselbe Integer in der Schleifenanweisung while
auswertet. Beachten Sie, dass die Schleife while
einen leeren Körper haben kann, damit dieses Beispiel funktioniert. Zunächst erstellt die Funktion main
einen separaten Thread, der die Funktion IncrementSeconds
ausführt. Direkt danach ruft der Hauptthread die Funktion DelayTenSeconds
auf, die in die Schleife geht, die nicht zurückkehrt, wenn die Variable seconds
den Wert 10
nicht überschreitet. Da der andere Thread bereits gleichzeitig damit begonnen hat, die Variable seconds
zu inkrementieren, wird der Hauptthread bald den geänderten Wert beobachten und von der Funktion zurückkehren.
#include <unistd.h>
#include <iostream>
#include <thread>
using std::cerr;
using std::cin;
using std::cout;
using std::endl;
volatile int seconds = 0;
void DelayTenSeconds() {
while (seconds < 10) {
usleep(500000);
cerr << "waiting..." << endl;
}
}
void IncrementSeconds() {
for (int i = 0; i < 10; ++i) {
sleep(1);
cerr << "incremented " << endl;
seconds = seconds + 1;
}
}
int main() {
struct timeval start {};
struct timeval end {};
std::thread th1;
th1 = std::thread(IncrementSeconds);
DelayTenSeconds();
th1.join();
return EXIT_SUCCESS;
}
Ausgabe:
waiting...
incremented
waiting...
....
waiting...
10.002481 sec
Als Ergebnis haben wir im Wesentlichen eine bedingte Verzögerungsfunktion implementiert, die wartet, bis das volatile
Objekt durch externe Einwirkung verändert wird. Nun könnte man feststellen, dass sich dieser Code genau gleich verhält, wenn der volatile
Qualifier entfernt und eine reguläre globale Variable verwendet wird, aber man sollte nicht vergessen, dass der Modifikationscodeblock von einer anderen Übersetzungseinheit oder einer externen Signalaktion stammen kann. Letztere Szenarien würden den Compiler zwingen, die DelayTenSeconds
-Schleife zu optimieren, da die Variable in der aktuellen Übersetzungseinheit nicht verändert wird.
#include <sys/time.h>
#include <unistd.h>
#include <iostream>
#include <thread>
using std::cerr;
using std::cin;
using std::cout;
using std::endl;
volatile int seconds = 0;
void DelayTenSeconds() {
while (seconds < 10) {
usleep(500000);
cerr << "waiting..." << endl;
}
}
float TimeDiff(struct timeval *start, struct timeval *end) {
return (end->tv_sec - start->tv_sec) + 1e-6 * (end->tv_usec - start->tv_usec);
}
void IncrementSeconds() {
for (int i = 0; i < 10; ++i) {
sleep(1);
cerr << "incremented " << endl;
seconds = seconds + 1;
}
}
int main() {
struct timeval start {};
struct timeval end {};
std::thread th1;
th1 = std::thread(IncrementSeconds);
gettimeofday(&start, nullptr);
DelayTenSeconds();
gettimeofday(&end, nullptr);
printf("%0.6f sec\n", TimeDiff(&start, &end));
th1.join();
return EXIT_SUCCESS;
}
Founder of DelftStack.com. Jinku has worked in the robotics and automotive industries for over 8 years. He sharpened his coding skills when he needed to do the automatic testing, data collection from remote servers and report creation from the endurance test. He is from an electrical/electronics engineering background but has expanded his interest to embedded electronics, embedded programming and front-/back-end programming.
LinkedIn Facebook