詳細はc++atomic原子プログラミング中のMemory Orderです。
概要
しかし、カーネルオブジェクトの同期に基づいて、高価なコンテキスト切り替え(ユーザ状態がカーネル状態に切り替わり、1000以上のcpu周期を占有する)をもたらす。もう一つの方法が必要です。原子指令です。
原子技術だけでは資源へのアクセス制御ができないので、単純な計数操作でも、正しいコードが出てくる可能性があります。
ここでの鍵はコンパイラとcpuで実施された並べ替え命令によって読み書き順が変化します。依存がない限り、コードの中で後ろのコマンドが前に行くことができます。コンパイラとCPUはこのようにします。
注1:単スレッドコードは乱序の問題に関心を持つ必要はありません。乱序は少なくともこの原則を保証します。単スレッドプログラムの実行行為を変えてはいけません。
注2:カーネルオブジェクトのマルチスレッドプログラムは、設計時に、それらの呼び出しポイントの乱れ(すでに暗黙的にmemory barrierを含む)を阻止しています。順序を乱す問題を考慮する必要はありません。
注3:ユーザモードのスレッドを同期させると、乱れの効果が現れます。
プログラマはc++11 atomicを使用して6種類のmemory orderを提供し、プログラミング言語レベルでコンパイラとcpuで実施される再配列命令挙動を制御することができる。
マルチスレッドプログラミングの場合、これらのフラグビットを通して原子変数を読み書きし、4つの同期モデルを組み合わせることができます。
Relaxed ordeng
Release-Acquire ordeng
Release-Coonsume ordeng
Sequentially-consistent ordeng
デフォルトでは、std:atomicはSequentially-consistent ordeng(最も厳しい同期モデル)を使用しています。しかし、いくつかのシーンでは、他の3つのorderingを合理的に使用して、コンパイラに生成されたコードを最適化させ、性能を向上させることができます。
Relaxed ordeng
このモデルの下で、std:atomicのロード()とstore()は全部memory(u)を持ちます。order.relaxedパラメータ。Relaxed orderingはただload()とstore()が原子操作であることを保証します。これ以外は、スレッドをまたぐ同期を提供しません。
まず簡単な例を見てください。
プログラムの実行順序がD->A->B->Cの場合、r 1==r 2==42が発生します。
ある操作が原子操作である限り、他の同期の保障が必要ではなく、Relaxed orderingを使用することができます。プログラムカウンターは典型的なアプリケーションシーンです。
このモデルでは、storeはmemory mouを使用します。order.release,loadはmemory_を使います。order.acquireこのモデルには二つの効果があります。第一はCPU命令の再配置を制限することができます。
(1)store()前のすべての読み書き操作は、このstore()の後ろに移動させてはいけません。/write-release語義
(2)ロード()後のすべての読み書き操作は、このロード()の前に移動させてはいけません。/read-acquire語義
このモデルは、Thread-1のstore()のその値が、Thread-2のロードに成功したら、Thread-1はstore()の前にメモリに対するすべての書き込み操作が行われ、Thread-2にとっては可視であることを保証することができます。
以下の例は、このモデルの原理を説明する。
まずAはBの後ろに移動することを許さない。
同様にDもCの前に移動することを許さない。
Cがwhileサイクルから退出した場合、B store()のその値をCが読み取ったと説明した場合、Thread-2は、Thread-1がBを実行する前のすべての書き込み操作(つまり、A)を確認できることを保証する。
Release-Acquire ordengを使用して、ダブルチェックロックモード(DLPP)を実現しました。
以下の件を例に挙げて説明します。
意味の取得と解放は、ロックを実現するための基礎であり、すなわち臨界領域を構成し、臨界領域内のメモリ動作は、乱順に臨界領域外に実行されない。
read-acquire
---------------------------------------------
all memory operation stay between the line(臨界区)
---------------------------------------------
write-release(リリースロック)
実現コードは以下の通りです。
②acquireメモリバリアを使用していますので、ロックは取得意味があります。
③releaseメモリバリアを使用していますので、unlockはリリース意味があります。
Release-Coonsume ordeng
このモデルでは、storeはmemory mouを使用します。order.release,loadはmemory_を使います。order.consumeこのモデルには二つの効果があります。第一はCPU命令の再配置を制限することができます。
(1)store()前のすべての読み書き操作は、このstore()の後ろに移動させてはいけません。
(2)ロード()後のこの原子変数に依存するすべての読み書き動作は、このロード()の前に移動することができません。
注:この原子変数に依存しない読み書き操作は、CPU命令が再配置される可能性があります。
以下の例は、このモデルの原理を説明する。
すべてはmemoryでorder.seqcstはパラメータの原子操作(同じ原子変数に限定されない)であり、全スレッドに対してグローバル順序(total order)がある。
そして二人は隣にいます。order.seqcst原子動作間の他の動作(非原子変数動作を含む)は、reorderがこの二つの隣接動作から外れている。
UE 4の下のメモリOrder
Atomic関連のテストコードは、アンレアルEngine\Engine\Source\Runtime\Core\Private\Tests\Misc\AtomicTest.cp
以上はc+atomic原子プログラミングの中のMemory Orderの詳しい内容を詳しく解かって、更にc+atomic原子プログラミングの中のMemoryOrderの資料に関して私達のその他の関連している文章に関心を持って下さい!
しかし、カーネルオブジェクトの同期に基づいて、高価なコンテキスト切り替え(ユーザ状態がカーネル状態に切り替わり、1000以上のcpu周期を占有する)をもたらす。もう一つの方法が必要です。原子指令です。
原子技術だけでは資源へのアクセス制御ができないので、単純な計数操作でも、正しいコードが出てくる可能性があります。
ここでの鍵はコンパイラとcpuで実施された並べ替え命令によって読み書き順が変化します。依存がない限り、コードの中で後ろのコマンドが前に行くことができます。コンパイラとCPUはこのようにします。
注1:単スレッドコードは乱序の問題に関心を持つ必要はありません。乱序は少なくともこの原則を保証します。単スレッドプログラムの実行行為を変えてはいけません。
注2:カーネルオブジェクトのマルチスレッドプログラムは、設計時に、それらの呼び出しポイントの乱れ(すでに暗黙的にmemory barrierを含む)を阻止しています。順序を乱す問題を考慮する必要はありません。
注3:ユーザモードのスレッドを同期させると、乱れの効果が現れます。
プログラマはc++11 atomicを使用して6種類のmemory orderを提供し、プログラミング言語レベルでコンパイラとcpuで実施される再配列命令挙動を制御することができる。
マルチスレッドプログラミングの場合、これらのフラグビットを通して原子変数を読み書きし、4つの同期モデルを組み合わせることができます。
Relaxed ordeng
Release-Acquire ordeng
Release-Coonsume ordeng
Sequentially-consistent ordeng
デフォルトでは、std:atomicはSequentially-consistent ordeng(最も厳しい同期モデル)を使用しています。しかし、いくつかのシーンでは、他の3つのorderingを合理的に使用して、コンパイラに生成されたコードを最適化させ、性能を向上させることができます。
Relaxed ordeng
このモデルの下で、std:atomicのロード()とstore()は全部memory(u)を持ちます。order.relaxedパラメータ。Relaxed orderingはただload()とstore()が原子操作であることを保証します。これ以外は、スレッドをまたぐ同期を提供しません。
まず簡単な例を見てください。
std::atomic<int> x = 0; // global variable
std::atomic<int> y = 0; // global variable
Thread-1: Thread-2:
r1 = y.load(memory_order_relaxed); // A r2 = x.load(memory_order_relaxed); // C
x.store(r1, memory_order_relaxed); // B y.store(42, memory_order_relaxed); // D
上のプログラムを実行したら、r 1==r 2==42が発生する可能性があります。この点を理解するのは難しくないです。コンパイラはCとDの実行順序を調整できるからです。プログラムの実行順序がD->A->B->Cの場合、r 1==r 2==42が発生します。
ある操作が原子操作である限り、他の同期の保障が必要ではなく、Relaxed orderingを使用することができます。プログラムカウンターは典型的なアプリケーションシーンです。
#include <cassert>
#include <vector>
#include <iostream>
#include <thread>
#include <atomic>
std::atomic<int> cnt = {0};
void f()
{
for (int n = 0; n < 1000; ++n) {
cnt.fetch_add(1, std::memory_order_relaxed);
}
}
int main()
{
std::vector<std::thread> v;
for (int n = 0; n < 10; ++n) {
v.emplace_back(f);
}
for (auto& t : v) {
t.join();
}
assert(cnt == 10000); // never failed
return 0;
}
Release-Acquire ordengこのモデルでは、storeはmemory mouを使用します。order.release,loadはmemory_を使います。order.acquireこのモデルには二つの効果があります。第一はCPU命令の再配置を制限することができます。
(1)store()前のすべての読み書き操作は、このstore()の後ろに移動させてはいけません。/write-release語義
(2)ロード()後のすべての読み書き操作は、このロード()の前に移動させてはいけません。/read-acquire語義
このモデルは、Thread-1のstore()のその値が、Thread-2のロードに成功したら、Thread-1はstore()の前にメモリに対するすべての書き込み操作が行われ、Thread-2にとっては可視であることを保証することができます。
以下の例は、このモデルの原理を説明する。
#include <thread>
#include <atomic>
#include <cassert>
#include <string>
std::atomic<bool> ready{ false };
int data = 0;
void producer()
{
data = 100; // A
ready.store(true, std::memory_order_release); // B
}
void consumer()
{
while (!ready.load(std::memory_order_acquire)) // C
;
assert(data == 100); // never failed // D
}
int main()
{
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
この過程を分析しましょう。まずAはBの後ろに移動することを許さない。
同様にDもCの前に移動することを許さない。
Cがwhileサイクルから退出した場合、B store()のその値をCが読み取ったと説明した場合、Thread-2は、Thread-1がBを実行する前のすべての書き込み操作(つまり、A)を確認できることを保証する。
Release-Acquire ordengを使用して、ダブルチェックロックモード(DLPP)を実現しました。
以下の件を例に挙げて説明します。
class Singleton
{
public:
static Singleton* get_instance() {
Singleton* tmp = instance_.load(std::memory_order_acquire);
if (tmp == nullptr) {
std::unique_lock<std::mutex> lk(mutex_);
tmp = instance_;
if (tmp == nullptr) {
tmp = new Singleton();
instance_.store(std::memory_order_release);
}
}
return tmp;
}
private:
Singleton() = default;
static std::atomic<Singleton*> instance_;
static std::mutex mutex_;
};
Release-Acquire ordengを使ってスピンロックを実現しました。意味の取得と解放は、ロックを実現するための基礎であり、すなわち臨界領域を構成し、臨界領域内のメモリ動作は、乱順に臨界領域外に実行されない。
read-acquire
---------------------------------------------
all memory operation stay between the line(臨界区)
---------------------------------------------
write-release(リリースロック)
実現コードは以下の通りです。
#include <atomic>
class simple_spin_lock
{
public:
simple_spin_lock() = default;
void lock()
{
while (flag.test_and_set(std::memory_order_acquire))
continue;
}
void unlock()
{
flag.clear(std::memory_order_release);
}
private:
simple_spin_lock(const simple_spin_lock&) = delete;
simple_spin_lock& operator =(const simple_spin_lock&) = delete;
std::atomic_flag flag = ATOMIC_FLAG_INIT;
};
①std::atoomic_flagGの操作は原子的で、同じ時間を保証します。一つのスレッドだけがロックに成功し、残りのスレッドは全部whileで循環します。②acquireメモリバリアを使用していますので、ロックは取得意味があります。
③releaseメモリバリアを使用していますので、unlockはリリース意味があります。
Release-Coonsume ordeng
このモデルでは、storeはmemory mouを使用します。order.release,loadはmemory_を使います。order.consumeこのモデルには二つの効果があります。第一はCPU命令の再配置を制限することができます。
(1)store()前のすべての読み書き操作は、このstore()の後ろに移動させてはいけません。
(2)ロード()後のこの原子変数に依存するすべての読み書き動作は、このロード()の前に移動することができません。
注:この原子変数に依存しない読み書き操作は、CPU命令が再配置される可能性があります。
以下の例は、このモデルの原理を説明する。
#include <thread>
#include <atomic>
#include <cassert>
#include <string>
std::atomic<std::string*> ptr;
int data;
// thread1
void producer()
{
std::string* p = new std::string("Hello"); // A
data = 42; // B
ptr.store(p, std::memory_order_release); // C
}
// thread2
void consumer()
{
std::string* p2;
while (!(p2 = ptr.load(std::memory_order_consume))) // D
;
assert(*p2 == "Hello"); //E always true: *p2 carries dependency from ptr
assert(data == 42); // F may be false: data does not carry dependency from ptr
}
int main()
{
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
Sequentially-consistent ordengすべてはmemoryでorder.seqcstはパラメータの原子操作(同じ原子変数に限定されない)であり、全スレッドに対してグローバル順序(total order)がある。
そして二人は隣にいます。order.seqcst原子動作間の他の動作(非原子変数動作を含む)は、reorderがこの二つの隣接動作から外れている。
UE 4の下のメモリOrder
enum class EMemoryOrder
{
// Provides no guarantees that the operation will be ordered relative to any other operation.
Relaxed,
// Establishes a single total order of all other atomic operations marked with this.
SequentiallyConsistent // Load Store
};
詳しくは、UrealEngine\Engine\Source\Runtime\Core\Public\Templates\Atomic.hAtomic関連のテストコードは、アンレアルEngine\Engine\Source\Runtime\Core\Private\Tests\Misc\AtomicTest.cp
以上はc+atomic原子プログラミングの中のMemory Orderの詳しい内容を詳しく解かって、更にc+atomic原子プログラミングの中のMemoryOrderの資料に関して私達のその他の関連している文章に関心を持って下さい!