5

アトミックメンバーを持つクラスがあり、コピーコンストラクターを書きたい:

struct Foo
{
    std::atomic<int> mInt;

    Foo() {}
    Foo(const Foo& pOther)
    {
        std::atomic_store(mInt, std::atomic_load(pOther.mInt, memory_order_relaxed), memory_order_relaxed);
    }
};

しかし、このコピーコンストラクターがいつどこで呼び出されるかわからないため、どの順序を使用する必要があるかわかりません。

relaxedコピー コンストラクターと代入演算子に順序付けを使用できますか?

4

3 に答える 3

3

いいえ、使い方がわからない場合は、memory_order_seq_cst安全のために使用する必要があります。を使用するmemory_order_relaxedと、命令が並べ替えられるという問題が発生する可能性があります。

于 2013-11-13T18:23:12.837 に答える
1

memory_order_relaxedコピー操作が別のスレッド上の他の操作と同期することになっている場合にのみ、より強力なメモリ順序付けが必要です。
ただし、スレッド セーフなコピー コンストラクターはほとんどの場合、何らかの外部同期または追加のミューテックスを必要とするため、これはほとんど当てはまりません。

于 2015-10-04T21:16:59.640 に答える
1

アトミックは共有状態std::atomic<T>用であるため、テンプレートはそのコピーコンストラクターを削除します。したがって、それらを別のアトミックにコピーすることは通常、必要なものではありません。

コピー コンストラクターを削除すると、クラスのユーザーは自分が何をしているのかを考え、1 つの値のアトミック ロードを実行し、そのコピーを別の場所に渡すことを文書化するようになります。(例atomic<some_struct> var1 (var2.load()))。C++11: アトミック<bool> メンバーを使用して移動コンストラクターを記述しますか?を参照してください。


for のコンストラクターstd::atomic<T> 自体は atomではないため、コンストラクターでのストアの順序について心配することはほとんど意味がありません (コンストラクターが他の関数の束を呼び出して、mInt別のスレッドが取得できる場所のアドレスを配置しない限り.. .)

さらに良いことに、アトミックストアをまったく実行する代わりに、コピーされた値を初期化子として使用します。(コピーコンストラクターでアトミックをコピーする非ロック方法も参照してください)。

これが問題になる可能性がある唯一の方法は、配置newを使用するなど、すでに未定義の動作を行っている場合にFoo、他のスレッドが読み書きできる新しいオブジェクトを既に共有されている場所に構築する場合です。 . これは明らかに非常識なので、そうしないでください。

クラスのメモリ順序付け動作をstd::atomic<T>のコンストラクターに一致させる (つまり、イニシャライザーを格納するためのものがない) ことは良い考えのようです。


ソースオペランドからのロードに順次整合性が必要かどうかは、呼び出し元だけが知っています。したがって、default=seq_cst を使用してメモリ順序引数を受け入れることにより、呼び出し元に選択させる必要があります (std::atomicこの場合、誰もがそれを望んでいる可能性があるためではなく、 との一貫性のためです)。そして、はい、これは正当な C++ です:デフォルトの引数を持つコピー コンストラクター

#include <atomic>

struct Foo
{
    std::atomic<int> mInt;

    Foo() {}
    Foo(const Foo& pOther, std::memory_order order = std::memory_order_seq_cst)
        : mInt(pOther.mInt.load(order))
    {}
};

これは、私が期待した方法でコンパイルされます。ロードの順序付けはありますが、ストアの順序付けはありません。(たとえば、ARM64 の asm 出力を見ると、ロードがldar取得ロードを実行するために使用されていることが示されていますが、ストアは単なる単純なものstrです)。

この呼び出し元(Godbolt コンパイラ エクスプローラー)を使用してテストしました。この呼び出し元は、スタック上に 1 つを構築し、そのアドレスを非インライン関数に渡し、そのアドレスを他のスレッドで使用できるようにする可能性があります。したがって、最適化することはできません。

void extf(Foo &);    // non-inline function

void test(const Foo *p) {
    Foo tmp(*p);
    extf(tmp);
}

他のスレッドがアドレスを利用できるようにするために何extf()をするにしても、リリースストアを使用する必要があります。これにより、そのアドレスを見る他のスレッドが適切に構築されFooた. これは通常の要件であり、イニシャライザがアトミックでなくても問題ない理由です。


(C++11 または私が認識しているハードウェアでは) 単一のアトミック操作として 2 つの異なるメモリ位置の間で移動することは不可能であるため、強い順序付けが役立つ可能性は低いことに注意してください。

原子性は観察者の目にのみ存在するため、そのような動きが原子的であるかどうかを定義することでさえ問題があります。2 つの記憶場所を同時に観察することは不可能なので、意味のない概念です。(それらが隣接していて、1 回のアトミック ロードで両方を取得できる場合を除きます)。

于 2017-09-05T01:11:30.980 に答える