web-dev-qa-db-ja.com

非プリミティブ型に対してstd :: atomic <>を効果的に使用する方法は?

std::atomic<>の定義 は、プリミティブまたはおそらくPODタイプに対する明らかな有用性を示しているようです。

実際にいつクラスに使用しますか?

いつクラスで使用する必要がありますかavoid

41
kfmfe04

_std::atomic_の操作は、簡単にコピー可能な型で利用できるようにすることは非常に基本的です。 _atomic<T>_を構築および破棄できます。タイプis_lock_free()かどうかを尋ねることができ、Tのコピーをロードおよび保存できます。また、さまざまな方法でTの値を交換できます。それが目的に十分であれば、明示的なロックを保持するよりも、それを行う方が良いかもしれません。

それらの操作が十分でない場合、たとえば、値に対してシーケンス操作を直接アトミックに実行する必要がある場合、またはオブジェクトが大きくてコピーが高価になる場合は、代わりに、管理する明示的なロックを保持する必要がありますより複雑な目標を達成するため、または_atomic<T>_を使用することに伴うすべてのコピーの実行を避けるため。

_// non-POD type that maintains an invariant a==b without any care for
// thread safety.
struct T { int b; }
struct S : private T {
    S(int n) : a{n}, b{n} {}
    void increment() { a++; b++; }
private:
    int a;
};

std::atomic<S> a{{5}}; // global variable

// how a thread might update the global variable without losing any
// other thread's updates.
S s = a.load();
S new_s;
do {
    new_s = s;
    new_s.increment(); // whatever modifications you want
} while (!a.compare_exchange_strong(s, new_s));
_

ご覧のとおり、これは基本的に値のコピーを取得し、コピーを変更してから、変更された値をコピーして、必要に応じて繰り返します。コピーに加える変更は、単純に単一のメンバー関数に限定されるのではなく、好きなだけ複雑にすることができます。

26
bames53

プリミティブ型およびPOD型で機能します。型はmemcpy- ableである必要があるため、より一般的なクラスがあります。

13
Pete Becker

標準では

アトミックテンプレートの特殊化とインスタンス化には、削除されたコピーコンストラクター、削除されたコピー割り当て演算子、およびconstexpr値コンストラクターが必要です。

それが厳密にピートベッカーによる答えと同じである場合、私は確信がありません。これは、memcpy対応のクラスだけでなく、自分のクラスに特化して自由に解釈できるように解釈します。

7
Johan Lundberg

この種のシナリオには、std :: mutexを好むでしょう。それにもかかわらず、私は貧弱な人のベンチマークを試して、シングルスレッド環境でstd :: atomicsとstd :: mutexを使用してバージョンのプロファイルを作成しました(したがって、完全に同期します)。

#include <chrono>
#include <atomic>
#include <mutex>

std::mutex _mux;
int i = 0;
int j = 0;
void a() {
    std::lock_guard<std::mutex> lock(_mux);
    i++;
    j++;
}

struct S {
    int k = 0;
    int l = 0;

    void doSomething() {
        k++;
        l++;
    }
};

std::atomic<S> s;
void b() {
    S tmp = s.load();
    S new_s;
    do {
        new_s = tmp;
        //new_s.doSomething(); // whatever modifications you want
        new_s.k++;
        new_s.l++;
    } while (!s.compare_exchange_strong(tmp, new_s));
}

void main(void) {

    std::chrono::high_resolution_clock clock;

    auto t1 = clock.now();
    for (int cnt = 0; cnt < 1000000; cnt++)
        a();
    auto diff1 = clock.now() - t1;

    auto t2 = clock.now();
    for (int cnt = 0; cnt < 1000000; cnt++)
        b();
    auto diff2 = clock.now() - t2;

    auto total = diff1.count() + diff2.count();
    auto frac1 = (double)diff1.count() / total;
    auto frac2 = (double)diff2.count() / total;
}

私のシステムでは、std :: mutexを使用したバージョンはstd :: atomicアプローチよりも高速でした。これは、値の追加のコピーが原因だと思います。さらに、マルチスレッド環境で使用すると、ビジーループはパフォーマンスにも影響を与える可能性があります。

要約すると、はい、さまざまな種類のポッドでstd :: atomicを使用できますが、ほとんどの場合、std :: mutexが選択の武器です。 std :: atomicで提示されたバージョンとしてのバグ。

1
pocketbroadcast