Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 755
kfmfe04 Avatar asked Dec 14 '12 20:12

kfmfe04


People also ask

Can Atomic be used with any data type?

atomic<> is not restricted to primitive types. It is permitted to use atomic<> with a type T that is trivially copyable.

What is std :: atomic used for?

Module std::sync::atomic. Atomic types provide primitive shared-memory communication between threads, and are the building blocks of other concurrent types. Rust atomics currently follow the same rules as C++20 atomics, specifically atomic_ref .

Is STD atomic copyable?

std::atomic is neither copyable nor movable. The compatibility macro _Atomic is provided in <stdatomic.

Is STD atomic thread-safe?

Yes, it would be threadsafe. Assuming of course there are no bugs in the std::atomic implementation - but it's not usually hard to get right. This is exactly what std::atomic is meant to do.


3 Answers

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.

like image 50
bames53 Avatar answered Oct 12 '22 16:10

bames53


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

like image 33
Pete Becker Avatar answered Oct 12 '22 15:10

Pete Becker


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).

like image 7
Johan Lundberg Avatar answered Oct 12 '22 14:10

Johan Lundberg