C++ の揮発性修飾子
この記事では、C++ の volatile
修飾子を紹介します。
volatile
修飾子を使用して、C++ の別のスレッドまたは外部アクションによって変更されるオブジェクトを示する
volatile
キーワードの動作は、一般にハードウェアに依存すると見なす必要があります。ユーザースペースアプリケーションの開発者は、さまざまなシナリオで修飾子がどのように解釈されるかについて、特定のコンパイラマニュアルを常に参照する必要があります。通常、volatile
キーワードは、指定されたオブジェクトをロード操作用に最適化してはならず、常にレジスタまたはキャッシュではなくメインメモリから取得する必要があることをコンパイラに通知します。ほとんどの場合ソフトウェアにアクセスできず、ハードウェアでのみ管理されるキャッシュの階層が複数あることに注意してください。ただし、コンパイラがレジスタ内のメモリ位置を読み込もうとすると、自動的にキャッシュされます。したがって、同じメモリ位置への結果としてのアクセスは、CPU に近いキャッシュラインから、RAM よりも数倍高速に実行できます。
一方、オブジェクトが何らかの外部信号または割り込みのようなルーチンによって変更された場合、キャッシュされた値は無効になるため、変更された値は RAM からアクセスする必要があります。したがって、volatile
オブジェクトへのアクセスは、それに応じてコンパイラによって処理されます。考えられるシナリオを示すために、グローバルな volatile
整数変数を変更する関数と、while
ループステートメントで同じ整数を評価する別の関数を実装します。この例が機能するためには、while
ループの本体が空である可能性があることに注意してください。最初に、main
関数は IncrementSeconds
関数を実行する別のスレッドを作成します。その直後、メインスレッドは DelayTenSeconds
関数を呼び出します。この関数は、seconds
変数が 10
の値を超えない場合に戻らないループに入ります。他のスレッドがすでに同時に seconds
変数のインクリメントを開始したため、メインスレッドはすぐに変更された値を監視し、関数から戻ります。
#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;
}
出力:
waiting...
incremented
waiting...
....
waiting...
10.002481 sec
その結果、基本的に、volatile
オブジェクトが外部アクションによって変更されるまで待機する条件付き遅延関数を実装しました。ここで、volatile
修飾子が削除され、通常のグローバル変数が使用されている場合、このコードはまったく同じように動作することに気付くかもしれませんが、変更コードブロックが別の変換ユニットまたは外部信号アクションからのものである可能性があることを忘れないでください。後者のシナリオでは、変数が現在の変換単位で変更されないため、コンパイラーは DelayTenSeconds
ループを最適化する必要があります。
#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;
}