Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 atomic classes and operations -- am I right

Am I right in the following assumptions:

  • I don't need to explicitly synchronize an access to the std::atomic<T> objects from different threads on any platform with my own synchronization objects
  • std::atomic<T> operations could be lock-free or non-lock-free, dependent on the platform
  • std::atomic_bool and std::atomic<bool> (and the other types like these) are the same things actually
  • std::atomic_flag is the only class that guarantees platform-independent lock-free operations by the standard

Also where can I find a useful info about std::memory_order and how to use it properly?

like image 600
FrozenHeart Avatar asked Feb 28 '14 08:02

FrozenHeart


People also ask

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.

What are atomic operations in C++?

(C++11) [edit] The atomic library provides components for fine-grained atomic operations allowing for lockless concurrent programming. Each atomic operation is indivisible with regards to any other atomic operation that involves the same object. Atomic objects are free of data races.

How do you declare an atomic variable in C++?

In order to solve this problem, C++ offers atomic variables that are thread-safe. The atomic type is implemented using mutex locks. If one thread acquires the mutex lock, then no other thread can acquire it until it is released by that particular thread.

What does STD atomic do?

std::atomic compiles to lock addq . The LOCK prefix makes the following inc fetch, modify and update memory atomically. our explicit inline assembly LOCK prefix compiles to almost the same thing as std::atomic , except that our inc is used instead of add .


1 Answers

Let's go through one by one.


  • I don't need to explicitly synchronize an access to the std::atomic<T> objects from different threads on any platform with my own synchronization objects

Yes, atomic objects are fully synchronized on all of their accessor methods.

The only time a data race can occur with access to the atomic types is during construction, but it involves constructing an atomic object A, passing its address to another thread via an atomic pointer using memory_order_relaxed to intentionally work around the sequential consistency of std::atomic, and then accessing A from that second thread. So, don't do that? :)

Speaking of construction, there are three ways to initialize your atomic types:

// Method 1: constructor
std::atomic<int> my_int(5);

// Method 2: atomic_init
std::atomic<int> my_int; // must be default constructed
std::atomic_init(&my_int, 5); // only allowed once

// Method 3: ATOMIC_VAR_INIT
// may be implemented using locks even if std::atomic<int> is lock-free
std::atomic<int> my_int = ATOMIC_VAR_INIT(5);

Using either of the two latter methods, the same data race possibility applies.


  • std::atomic<T> operations could be lock-free or non-lock-free, dependent on the platform

Correct. For all integral types, there are macros you can check that tell you whether a given atomic specialization is not, sometimes, or always lock-free. The value of the macros is 0, 1, or 2 respectively for these three cases. The full list of macros is taken from §29.4 of the standard, where unspecified is their stand-in for "0, 1, or 2":

#define ATOMIC_BOOL_LOCK_FREE unspecified 
#define ATOMIC_CHAR_LOCK_FREE unspecified 
#define ATOMIC_CHAR16_T_LOCK_FREE unspecified 
#define ATOMIC_CHAR32_T_LOCK_FREE unspecified 
#define ATOMIC_WCHAR_T_LOCK_FREE unspecified 
#define ATOMIC_SHORT_LOCK_FREE unspecified 
#define ATOMIC_INT_LOCK_FREE unspecified 
#define ATOMIC_LONG_LOCK_FREE unspecified 
#define ATOMIC_LLONG_LOCK_FREE unspecified 
#define ATOMIC_POINTER_LOCK_FREE unspecified

Note that these defines apply to both the unsigned and signed variants of the corresponding types.

In the case that the #define is 1, you have to check at runtime. This is accomplished as follows:

std::atomic<int> my_int;

if (my_int.is_lock_free()) {
    // do lock-free stuff
}

if (std::atomic_is_lock_free(&my_int)) {
    // also do lock-free stuff
}

  • std::atomic_bool and std::atomic<bool> (and the other types like these) are the same things actually

Yes, these are just typedefs for your convenience. The full list is found in Table 194 of the standard:

Named type      | Integral argument type
----------------+-----------------------
atomic_char     | char
atomic_schar    | signed char
atomic_uchar    | unsigned char
atomic_short    | short
atomic_ushort   | unsigned short
atomic_int      | int
atomic_uint     | unsigned int
atomic_long     | long
atomic_ulong    | unsigned long
atomic_llong    | long long
atomic_ullong   | unsigned long long
atomic_char16_t | char16_t
atomic_char32_t | char32_t
atomic_wchar_t  | wchar_t

  • std::atomic_flag is the only class that guarantees platform-independent lock-free operations by the standard

Correct, as guaranteed by §29.7/2 in the standard.

Note that there's no guarantee on the initialization state of atomic_flag unless you initialize it with the macro as follows:

std::atomic_flag guard = ATOMIC_FLAG_INIT; // guaranteed to be initialized cleared

There is a similar macro for the other atomic types,

The standard does not specify if atomic_flag could experience the same data race other atomic types could have during construction.


  • Also where can I find a useful info about std::memory_order and how to use it properly?

As suggested by @WhozCraig, cppreference.com has the best reference.

And as @erenon suggests, Boost.Atomic has a great essay on how to use memory fences for lock-free programming.

like image 176
tclamb Avatar answered Sep 30 '22 00:09

tclamb