简体   繁体   中英

How to use std::atomic<> effectively for non-primitive types?

The definitions for std::atomic<> seem to show its obvious usefulness for primitive or perhaps POD-types.

When would you actually use it for classes?

When should you avoid using it for classes?

The operations std::atomic makes available on any trivially copyable type are pretty basic. You can construct and destroy atomic<T> , you can ask if the type is_lock_free() , you can load and store copies of T , and you can exchange values of T in various ways. If that's sufficient for your purpose then you might be better off doing that than holding an explicit lock.

If those operations aren't sufficient, if for example you need to atomically perform a sequence operations directly on the value, or if the object is large enough that copying is expensive, then instead you would probably want to hold an explicit lock which you manage to achieve your more complex goals or avoid doing all the copies that using atomic<T> would involve.

// 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));

As you can see, this basically gets a copy of the value, modifies the copy, then tries to copy the modified value back, repeating as necessary. The modifications you make to the copy can be as complex as you like, not simply limited to single member functions.

It works for primitive and POD types. The type must be memcpy -able, so more general classes are out.

The standard say that

Specializations and instantiations of the atomic template shall have a deleted copy constructor, a deleted copy assignment operator, and a constexpr value constructor.

If that is strictly the same as the answer by Pete Becker, I'm not sure. I interpret this such that you are free to specialize on your own class (not only memcpy-able classes).

I'd prefer std::mutex for this kind of scenarios. Nevertheless I've tried a poor mans benchmark to profile a version with std::atomics and std::mutex in a single threaded (and thus perfectly sync) environment.

#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;
}

on my system the version using std::mutex was faster than the std::atomic approach. I think this is caused by the additional copying of the values. Further, if used in a multithreaded environment, the the busy looping can affect performance too.

Summing up, yes it is possible to use std::atomic with various pod types, but in most cases std::mutex is the weapon of choice, as it is intentionally easier to understand what is going on, and therefore is not as prone to bugs as the version presented with the std::atomic.

With Visual Studio 2017 I have experienced compiler error C2338 due to "alignment" issues when attempting to use std::atomic with a class; you're much better off using a std::mutex which I ended up doing anyway.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM