What exactly is std::atomic?

user4386938

I understand that std::atomic<> is an atomic object. But atomic to what extent? To my understanding an operation can be atomic. What exactly is meant by making an object atomic? For example if there are two threads concurrently executing the following code:

a = a + 12;

Then is the entire operation (say add_twelve_to(int)) atomic? Or are changes made to the variable atomic (so operator=())?

Mateusz Grzejek

Each instantiation and full specialization of std::atomic<> represents a type that different threads can simultaneously operate on (their instances), without raising undefined behavior:

Objects of atomic types are the only C++ objects that are free from data races; that is, if one thread writes to an atomic object while another thread reads from it, the behavior is well-defined.

In addition, accesses to atomic objects may establish inter-thread synchronization and order non-atomic memory accesses as specified by std::memory_order.

std::atomic<> wraps operations that, in pre-C++ 11 times, had to be performed using (for example) interlocked functions with MSVC or atomic bultins in case of GCC.

Also, std::atomic<> gives you more control by allowing various memory orders that specify synchronization and ordering constraints. If you want to read more about C++ 11 atomics and memory model, these links may be useful:

Note that, for typical use cases, you would probably use overloaded arithmetic operators or another set of them:

std::atomic<long> value(0);
value++; //This is an atomic op
value += 5; //And so is this

Because operator syntax does not allow you to specify the memory order, these operations will be performed with std::memory_order_seq_cst, as this is the default order for all atomic operations in C++ 11. It guarantees sequential consistency (total global ordering) between all atomic operations.

In some cases, however, this may not be required (and nothing comes for free), so you may want to use more explicit form:

std::atomic<long> value {0};
value.fetch_add(1, std::memory_order_relaxed); // Atomic, but there are no synchronization or ordering constraints
value.fetch_add(5, std::memory_order_release); // Atomic, performs 'release' operation

Now, your example:

a = a + 12;

will not evaluate to a single atomic op: it will result in a.load() (which is atomic itself), then addition between this value and 12 and a.store() (also atomic) of final result. As I noted earlier, std::memory_order_seq_cst will be used here.

However, if you write a += 12, it will be an atomic operation (as I noted before) and is roughly equivalent to a.fetch_add(12, std::memory_order_seq_cst).

As for your comment:

A regular int has atomic loads and stores. Whats the point of wrapping it with atomic<>?

Your statement is only true for architectures that provide such guarantee of atomicity for stores and/or loads. There are architectures that do not do this. Also, it is usually required that operations must be performed on word-/dword-aligned address to be atomic std::atomic<> is something that is guaranteed to be atomic on every platform, without additional requirements. Moreover, it allows you to write code like this:

void* sharedData = nullptr;
std::atomic<int> ready_flag = 0;

// Thread 1
void produce()
{
    sharedData = generateData();
    ready_flag.store(1, std::memory_order_release);
}

// Thread 2
void consume()
{
    while (ready_flag.load(std::memory_order_acquire) == 0)
    {
        std::this_thread::yield();
    }

    assert(sharedData != nullptr); // will never trigger
    processData(sharedData);
}

Note that assertion condition will always be true (and thus, will never trigger), so you can always be sure that data is ready after while loop exits. That is because:

  • store() to the flag is performed after sharedData is set (we assume that generateData() always returns something useful, in particular, never returns NULL) and uses std::memory_order_release order:

memory_order_release

A store operation with this memory order performs the release operation: no reads or writes in the current thread can be reordered after this store. All writes in the current thread are visible in other threads that acquire the same atomic variable

  • sharedData is used after while loop exits, and thus after load() from flag will return a non-zero value. load() uses std::memory_order_acquire order:

std::memory_order_acquire

A load operation with this memory order performs the acquire operation on the affected memory location: no reads or writes in the current thread can be reordered before this load. All writes in other threads that release the same atomic variable are visible in the current thread.

This gives you precise control over the synchronization and allows you to explicitly specify how your code may/may not/will/will not behave. This would not be possible if only guarantee was the atomicity itself. Especially when it comes to very interesting sync models like the release-consume ordering.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Java

What exactly is std::labs() there for?

From Java

What exactly is decltype(std::get<0>(tup))?

From Java

What exactly is assigned by the `=` assigment to the new std::string?

From Dev

What's the usecase of volatile operations on std::atomic<T>?

From Dev

what is the difference between using ATOMIC_FLAG_INIT and std::atomic_flag::clear

From Dev

what is the difference between using ATOMIC_FLAG_INIT and std::atomic_flag::clear

From Dev

atomic read then write with std::atomic

From Java

What exactly std::function() does when stored in a container?

From Dev

What exactly is the returned data type for a particular std::bind?

From Dev

What is ${@:$#} exactly?

From Java

Where is the lock for a std::atomic?

From Dev

Segfault in std::atomic load?

From Dev

Correct usage of std::atomic

From Dev

std::atomic as a value of std::map

From Dev

Atomic operations, std::atomic<> and ordering of writes

From Dev

Is assignment to a std::atomic<double> variable guaranteed to be atomic?

From Dev

What is an "atomic unit of data"?

From Dev

Java: What is an atomic number?

From Java

What are atomic operations for newbies?

From Dev

Java: What is an atomic number?

From Dev

What exactly happens when we use rvalue references and how does std::move work?

From Dev

What exactly happens when we use rvalue references and how does std::move work?

From Dev

std::atomic_store and std::atomic_exchange do not exchange

From Dev

use of deleted function - std::atomic

From Dev

Comparison semantics with std::atomic types

From Dev

Using std::atomic with aligned classes

From Dev

Using std::atomic from C

From Dev

std::atomic<> operator++ in MSVC

From Dev

Is std::atomic signal-safe?

Related Related

HotTag

Archive