C ++並列プログラミング:C ++ 11アトミック操作とメモリモデル
まず、知っておく必要があります
次のように図1に示すように、プログラムの最終的な出力に影響を与える因子であります
-
(ナンセンスです...)コード書かれた順
ここでは詳しく述べない:サンプルC ++コードは、以下の式で与えられます。
#include <thread> #include <atomic> #include <iostream> using namespace std; atomic<int> a {0}; atomic<int> b {0}; int ValueSet(int) { int t = 1; a = t; b = 2; } int Observer(int) { cout << "(" << a << ", " << b << ")" << endl; // 可能有多种输出 } int main() { thread t1(ValueSet, 0); thread t2(Observer, 0); t1.join(); t2.join(); cout << "Got (" << a << ", " << b << ")" << endl; // Got (1, 2) }
持ってgoes'll言って、あなたはコード・シーケンスは、必ずしも前回の実行の結果ではなく、見ます...
-
プラットフォームのCPUの機械語命令の強さのメモリモデルの実行順序
-
メモリモデル
メモリモデルは(もすることができ、それはアセンブリ言語命令として又はリーダー)マシン命令を表す、ハードウェア概念に通常、プロセッサによって実行されるどのような順序です。 -
(x86など)、強いメモリモデル:実行するためのCPUの機械語命令を順次生成
-
(例えば、PowerPCのような)弱いメモリ・モデル:CPUマシンで生成された命令は、(順不同)順序に従って行わなくてもよいです
-
注:なぜ弱いシーケンシャル・メモリ・モデルはありますか?
プロセッサはさらに、命令並列度、そのような高性能の実行を探求することができるように簡単に説明すると弱いシーケンシャル・メモリ・モデル。 -
上記次のサンプルコード「= 1、T、A = T; B = 2;」で生成された擬似アセンブリコード(ここでは近似見マシン命令)
1: Loadi reg3, 1; # 将立即数1放入寄存器reg3 2: Move reg4, reg3; # 将reg3的数据放入reg4 3: Store reg4, a; # 将寄存器reg4中的数据存入内存地址a 4: Loadi reg5, 2; # 将立即数2放入寄存器reg5 5: Store reg5, b; # 将寄存器reg5中的数据存入内存地址b
CPU強いメモリモデルの実行順序は、常に1-> 2-> 3-> 4-> 5
順次実行CPU弱いメモリ・モデルは、次のとおり命令ので、1-> 2-> 3-> 4-> 5であってもよいです1、2、3、およびノーオペレーション命令4,5影響順序(異なるレジスタと異なるメモリアドレスを使用して)、それがあってもよい1-> 4-> 2-> 5-> 3 -
-
コンパイラのコンパイラの最適化
-
弱いメモリ・モデルを再順序付けするための命令(アセンブラ命令として約ここに)最終的なメモリモデルコンパイラ生成されたマシン命令の強さで実行されているコンパイラの意志コードは、命令プラスメモリバリアの特定の実行順序を必要とします。
-
指示手段アセンブラ生成された強力なメモリモデル
a及びbは原子変数のデフォルトであるため、コンパイラを防止するために、すなわち、シーケンシャル一貫原理を取ることで、実行の順序は、常に強いメモリモデルCPU 1-> 2-> 3-> 4であるので、命令関連ABの最適化を並べ替え - > 5
1: Loadi reg3, 1; # 将立即数1放入寄存器reg3 2: Move reg4, reg3; # 将reg3的数据放入reg4 3: Store reg4, a; # 将寄存器reg4中的数据存入内存地址a 4: Loadi reg5, 2; # 将立即数2放入寄存器reg5 5: Store reg5, b; # 将寄存器reg5中的数据存入内存地址b
-
弱いメモリ・モデルアセンブリ命令を生成します
a及びbは原子であるので、変数のデフォルト値は、ABはアセンブラコードがそれほど強く発生最適化一貫したメモリモデルが生成関連する命令を並べ替えるコンパイラを禁止シーケンシャル一貫原理を取ることであるが、弱いメモリ・モデルは、CPUであるためオーダー実行、それはまた、追加のメモリコンパイラフェンス、強制命令の実行順序を必要
1-> 2-> 3-> 4-> 5。
同期:命令がプロセッサの前に完了した後、このコマンドは、パイプライン内に押し込まれていますsyncコマンドの実行後に(パイプラインを空にする)。その結果、同期の前に説明書を操作することは、常に完全に同期した後の命令に先行します。
1: Loadi reg3, 1; # 将立即数1放入寄存器reg3 2: Move reg4, reg3; # 将reg3的数据放入reg4 3: Store reg4, a; # 将寄存器reg4中的数据存入内存地址a 4: Sync # 内存栅栏 5: Loadi reg5, 2; # 将立即数2放入寄存器reg5 6: Store reg5, b; # 将寄存器reg5中的数据存入内存地址b
-
二、C ++ 11のメモリモデル
1、メモリモデルの列挙
列挙値 | 定義されたルール |
---|---|
memory_order_relaxed | これは、いかなる保証の実行順序を作成しません。 |
memory_order_acquire | このスレッドすべての後続の読み出し動作は、操作部原子が完了した後に行わなければなりません |
memory_order_release | 書き込み操作が完了する前に、このスレッドは、このセクションのすべての後にアトミック操作を行うことができます |
memory_order_acq_rel | そしてmemory_order_releaseタグはmemory_order_acquireが含まれています |
memory_order_consume | アトミック操作のこのタイプに関するすべての後続のスレッドの存在は、操作部原子が完了した後に行わなければなりません |
memory_order_seq_cst | すべてのアクセスは順番に実行されています |
2、簡単な分類メモリモデル
-
アトミック操作は、(ストア)に格納されている:memory_order_release、memory_order_seq_cst、memorey_order_relaxed使用することができます。
-
アトミック動作(負荷)を読み出す:memorey_order_relaxed、memory_order_consume、memory_order_acquire、memory_order_seq_cstを使用することができます。
-
操作RMW(リードモディファイライト):すなわち、そのような言及以前atomic_flag型test_and_set()操作などの同時読み取りおよび書き込み操作を必要とします。別の例示atomic_compare_exchange原子クラステンプレート()オペレーションを同時に読み書きするために必要とされます。RMW操作はmemorey_order_relaxed、memory_order_consume、memory_order_acquire、memory_order_release、memory_order_acq_rel、memory_order_seq_cstを使用することができます。
-
フォーム「演算子=」、「演算子+ =」機能のいくつかは、実際には、単にパラメータとして動作memory_order_seq_cst memory_order原子をパッケージ化されています。
一般的に使用される3、C ++ 11のメモリモデル
#include <thread>
#include <atomic>
#include <iostream>
using namespace std;
atomic<int> a;
atomic<int> b;
int Thread1(int) {
int t = 1;
a.store(t, memory_order_seq_cst);
b.store(2, memory_order_seq_cst);
}
int Thread2(int) {
while(b.load(memory_order_seq_cst) != 2); // 自旋等待
cout << a.load(memory_order_seq_cst) << endl;
}
int main() {
thread t1(Thread1, 0);
thread t2(Thread2, 0);
t1.join();
t2.join();
return 0;
}
#include <thread>
#include <atomic>
#include <iostream>
using namespace std;
atomic<int> a;
atomic<int> b;
int Thread1(int) {
int t = 1;
a.store(t, memory_order_relaxed);
b.store(2, memory_order_relaxed);
}
int Thread2(int) {
while(b.load(memory_order_relaxed) != 2); // 自旋等待
cout << a.load(memory_order_relaxed) << endl;
}
int main() {
thread t1(Thread1, 0);
thread t2(Thread2, 0);
t1.join();
t2.join();
return 0;
}
-
リリース獲得型
b.storeの前a.storeが発生
発生a.laod前b.load
完全コードの正しさが実行されている保証、すなわちB 2の値は、1の値は、他にも決定される時間。print文は、スピンウェイトの値の前に印刷されていません
#include <thread>
#include <atomic>
#include <iostream>
using namespace std;
atomic<int> a;
atomic<int> b;
int Thread1(int) {
int t = 1;
a.store(t, memory_order_relaxed);
b.store(2, memory_order_release); // 本原子操作前所有的写原子操作必须完成
}
int Thread2(int) {
while(b.load(memory_order_acquire) != 2); // 本原子操作必须完成才能执行之后所有的读原子操作
cout << a.load(memory_order_relaxed) << endl; // 1
}
int main() {
thread t1(Thread1, 0);
thread t2(Thread2, 0);
t1.join();
t2.join();
return 0;
}
-
リリース - 消費型
このようなメモリ順序付けを保証しptr.load(memory_order_consume)が発生しなければならないような操作ソリューションに先立っ* ptrを参照
しdata.load前に発生しない保証(memory_order_relaxed)
#include <thread>
#include <atomic>
#include <cassert>
#include <string>
using namespace std;
atomic<string*> ptr;
atomic<int> data;
void Producer() {
string* p = new string("Hello");
data.store(42, memory_order_relaxed);
ptr.store(p, memory_order_release);
}
void Consumer() {
string* p2;
while (!(p2 = ptr.load(memory_order_consume)))
;
assert(*p2 == "Hello"); // 总是相等
assert(data.load(memory_order_relaxed) == 42); // 可能断言失败
}
int main() {
thread t1(Producer);
thread t2(Consumer);
t1.join();
t2.join();
}