MultiThreading
 All Modules Pages

Using atomics the normal way - high level interface

Instead of using explicit lock mechanisms you can define any variable of integral type (bool, integer, pointer type) as "atomic". Then the locking will be done automatically each time you access the variable.

Basic Example

The following sample illustrates safe data access to the variables of the preceding section:

#include <atomic>
// Global data definition with initial values
int g_someCounter = 0;
double g_someValue = 0.0;
// Define and initialize the atomic object
// to guarantee proper locking
std::atomic<bool> g_ready(false); //<##

Some worker thread changes data and signals the end of his work by setting the ready flag.

// Thread changing global data
g_someCounter = 47;
g_someValue = 3.14;
g_ready.store(true); //<##
// "release operation"
// ensures that all memory operations are executed
// and are visible to other threads, i.e.
// all variables have already changed their values

Some other thread wants to process the changed data:

// Thread using global data
if (g_ready.load()) //<##
// "aquire operation" for affected memory
{
myCounter += g_someCounter;
myValue = g_someValue * 2;
}

Most used features

Method Description
a.store(val) safely sets the given value
a.load() safely returns the current value
a+=val, a-=val,
a++, ++a,
a&=val, a|=val
provides the usual functionality, implicitly calls load() or store()

Limitations

Using atomics the "relaxed" way - low-level interface only for experts

Reading and writing shared data when running several threads on a multicore system may lead to problems with the execution order of C++ statements. For example a reader thread may observe value changes in a different order than the writer thread has executed these changes.

If you use std::atomic the way described in the previous section, then load() and store() are used with default argument "std::memory_order_seq_cst" ("sequential consistent memory order"). With this settings you can assume that all threads see the same order of changes.

To allow a more optimized code generation, it is possible to weaken the requirements for the memory model. For writing data the "std::memory_order_release" and for reading data "std::memory_order_aquire" would be sufficient.

If there are no dependencies to other data, you may even use "std::memory_order_relaxed":

g_ready.store(true,std::memory_order_relaxed);
if (g_ready.load(std::memory_order_relaxed))
{
...
}

Recommendation

If you have no need for optimization and if you are not yet a multithreading expert always use the high-level interface for atomics.