Подтвердить что ты не робот

Как эффективно использовать std:: atomic <> для не-примитивных типов?

Определения для std::atomic<>, по-видимому, показывают его очевидную полезность для примитивных или, возможно, типов POD.

Когда вы на самом деле используете его для классов?

Когда вы избегаете, используя его для классов?

4b9b3361

Ответ 1

Операции 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 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));

Как вы можете видеть, это в основном получает копию значения, изменяет копию, затем пытается скопировать измененное значение обратно, повторяя при необходимости. Модификации, которые вы делаете в копии, могут быть такими же сложными, как вам нравится, а не просто ограничены функциями одного элемента.

Ответ 2

Он работает для примитивных и POD типов. Тип должен быть memcpy -able, поэтому более общие классы отсутствуют.

Ответ 3

Стандарт говорит, что

Специализации и экземпляры атомного шаблона должны иметь удаленный экземпляр копии, оператор делегирования удаленной копии и конструктор constexpr.

Если это строго совпадает с ответом Пита Беккера, я не уверен. Я интерпретирую это так, что вы можете специализироваться на своем классе (не только memcpy-able classes).

Ответ 4

Я бы предпочел 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.

Ответ 5

С Visual Studio 2017 у меня возникла ошибка компилятора C2338 из-за проблем с выравниванием при попытке использования std:: atomic с классом; вам гораздо лучше использовать std:: mutex, который я все равно сделал.