メモリモデル
C++ 抽象機械の目的のためにコンピュータのメモリ記憶域の意味論を定義します。
C++ プログラムで利用可能なメ��リは1個以上の連続したバイトの並びです。 メモリ内のそれぞれのバイトは一意なアドレスを持ちます。
目次 |
[編集] バイト
バイトはメモリのアドレス可能な最小の単位です。 連続するビットの並びとして定義され、UTF-8
コードユニットの任意の値 (256 個の別々の値) および (C++14以上)基本実行文字集合 (シングルバイトであることが要求される96個の文字) の任意のメンバを保持するのに十分な大きさです。 C と同様に、 C++ は8ビット以上のサイズのバイトをサポートします。
char、unsigned char、および signed char 型は記憶域と値表現の両方に対して1バイト使用します。 1バイトのビット数は CHAR_BIT または std::numeric_limits<unsigned char>::digits としてアクセス可能です。
[編集] メモリ位置
メモリ位置は以下のいずれかです。
ノート: 言語の様々な機能 (参照とか仮想関数とか) は、プログラムからはアクセス可能でないけれども処理系によって管理される追加のメモリ位置に影響を与えることがあります。
struct S { char a; // メモリ位置 #1 int b : 5; // メモリ位置 #2 int c : 11, // メモリ位置 #2 (続き) : 0, d : 8; // メモリ位置 #3 struct { int ee : 8; // メモリ位置 #4 } e; } obj; // オブジェクト「obj」は4つの別々のメモリ位置から構成されます。
[編集] スレッドとデータ競合
実行のスレッドは、 std::thread::thread、 std::async、またはその他の方法によってトップレベル関数の呼び出しで始まる、プログラム内の制御の流れです。
潜在的にあらゆるスレッドがプログラム内のあらゆるオブジェクトにアクセスし得ます (自動およびスレッドローカル記憶域期間を持つオブジェクトでもポインタまたは参照を通して別のスレッドからアクセスされる可能性があります)。
異なる実行のスレッドが異なるメモリ位置にアクセスすることは常に許されています。 干渉せず、同期の要件もありません。
ある式の評価があるメモリ位置に書き込み、別の式の評価がその同じメモリ位置を読み込むまたは変更するとき、それらの式は衝突すると言います。 衝突する2つの評価を持つプログラムは、以下の場合は除き、データ競合を持ちます。
- 両方の評価が同じスレッド上または同じシグナルハンドラ上で実行された。
- 衝突する評価が両方ともアトミック操作である (std::atomic を参照してください)。
- 衝突する評価の一方が他方に対して先行発生する (std::memory_order を参照してください)。
データ競合が発生した場合、プログラムの動作は未定義です。
(特に、 std::mutex の解放は、別のスレッドによるその同じミューテックスの取得に対して同期し、それゆえ先行発生します。 これはデータ競合に対する保護のためにミューテックスロックを使用することを可能にします。)
int cnt = 0; auto f = [&]{cnt++;}; std::thread t1{f}, t2{f}, t3{f}; // 未定義動作
std::atomic<int> cnt{0}; auto f = [&]{cnt++;}; std::thread t1{f}, t2{f}, t3{f}; // OK
[編集] メモリ順序
あるスレッドがあるメモリ位置から読み込むとき、それは初期値を見るかもしれませんし、同じスレッドによって書かれた値を見るかもしれませんし、別のスレッドによって書かれた値を見るかもしれません。 スレッドによって行われた書き込みが他のスレッドに可視となる順序の詳細については std::memory_order を参照してください。
[編集] 前方進行
[編集] オブストラクションフリー
標準ライブラリ関数によってブロックされていないスレッドのうちひとつだけがロックフリーなアトミック関数を実行するとき、その実行は完了することが保証されます (標準ライブラリのロックフリー操作はすべてオブストラクションフリーです)。
[編集] ロックフリー
1つ以上のロックフリーなアトミック関数が並行的に実行されるとき、それらのうち少なくともひとつは完了することが保証されます (標準ライブラリのロックフリー操作はすべてロックフリーです。 他のスレッドによっていつまでも実質的にロックされ続ける (お互いにキャッシュラインを奪い合うなど) ことがないようにするのは処理系の仕事です)。
[編集] 進行保証
有効な C++ のプログラムでは、すべてのスレッドがいずれは以下のいずれかを行います。
- 終了する。
- 入出力ライブラリ関数の呼び出しを行う。
- volatile な glvalue を通したアクセスを行う。
- アトミック操作または同期操作を行う。
実行のスレッドがこれらの観察可能な動作のいずれも行うことなく永遠に実行することはできません。
これは無限再帰や無限ループ (for 文として実装されているか goto でループしているかその他の方法かにかかわらず) を持つプログラムは未定義動作であることを意味することに注意してください。 これは、観察可能な動作を持たないすべてのループを、最終的に終了するかどうかを証明する必要なく削除することを、コンパイラに許可します。
上記の実行ステップ (入出力、 volatile、アトミック、または同期) のいずれかを行う、標準ライブラリ関数内でブロックする、またはブロックされていない並行スレッドのために完了できないアトミックロックフリー関数を呼ぶ場合、スレッドは進行すると言います。
並行前方進行スレッドが並行前方進行保証を提供する場合、そのスレッドは、他のスレッド (もしあれば) が進行するかどうかにかかわらず、終了しない限り、有限の時間内に進行 (上で定義した意味で) します。 標準は、メインスレッドおよび std::thread によって開始したスレッドが並行前方進行保証を提供することを、推奨はしていますが要求はしていません。 並列前方進行スレッドが並列前方進行保証を提供する場合、処理系は、そのスレッドが何らかの実行ステップ (入出力、 volatile、アトミック、または同期) をまだ実行していない場合は、いずれ進行することを保証することは要求されませんが、いったんそのスレッドがステップを実行すれば、並行前方進行保証を提供します (このルールは適当な順序でタスクを実行するスレッドプール内のスレッドを説明しています)。 弱並列前方進行スレッドが弱並列前方進行保証を提供する場合、他のスレッドが進行するかどうかにかかわらず、そのスレッドがいずれ進行することは保証されません。 そのようなスレッドは前方進行保証移譲を持つブロックによってであれば進行することを保証できます。 スレッド P がスレッドの集合 S の完了をこの方法でブロックする場合、 S 内のスレッドの少なくともひとつは P と同じかそれより強い前方進行保証を提供します。 そのスレッドが完了すれば、 S 内の別のスレッドが同様に強化されます。 集合が空になれば、 P はブロック解除されます。 C++ 標準ライブラリの並列アルゴリズムは、未規定なライブラリ管理されたスレッドの集合の完了に対する前方進行移譲付きで、ブロックします。 |
(C++17以上) |
[編集] 関連項目
メモリモデル の C言語リファレンス
|