![]() |
Home | Libraries | People | FAQ | More |
The library contains header-only and compiled parts. The library is header-only for lock-free cases but requires a separate binary to implement the lock-based emulation and waiting and notifying operations on some platforms. Users are able to detect whether linking to the compiled part is required by checking the feature macros.
The following macros affect library behavior:
Macro |
Description |
---|---|
|
Binary logarithm of the number of locks in the internal lock pool used by Boost.Atomic to implement lock-based atomic operations and waiting and notifying operations on some platforms. Must be an integer in range from 0 to 16, the default value is 8. Only has effect when building Boost.Atomic. |
|
Affects 32-bit x86 Oracle Studio builds. When defined, the library
assumes the target CPU does not support |
|
Affects 64-bit x86 MSVC and Oracle Studio builds. When defined,
the library assumes the target CPU does not support |
|
When defined, support for floating point operations is disabled. Floating point types shall be treated similar to trivially copyable structs and no capability macros will be defined. |
|
Affects compilation on Darwin systems (Mac OS, iOS, tvOS, watchOS).
When defined, disables use of |
|
When defined, all operations are implemented with locks. This is mostly used for testing and should not be used in real world projects. |
|
Control library linking. If defined, the library assumes dynamic linking, otherwise static. The latter macro affects all Boost libraries, not just Boost.Atomic. |
|
Control library auto-linking on Windows. When defined, disables auto-linking. The latter macro affects all Boost libraries, not just Boost.Atomic. |
Besides macros, it is important to specify the correct compiler options for the target CPU. With GCC and compatible compilers this affects whether particular atomic operations are lock-free or not.
Boost building process is described in the Getting Started guide. For example, you can build Boost.Atomic with the following command line:
b2 --with-atomic variant=release instruction-set=core2 stage
#include <boost/memory_order.hpp>
The scoped enumeration boost::memory_order
defines the
following values to represent memory ordering constraints:
Constant |
Description |
---|---|
|
No ordering constraint. Informally speaking, following operations
may be reordered before, preceding operations may be reordered
after the atomic operation. This constraint is suitable only when
either a) further operations do not depend on the outcome of the
atomic operation or b) ordering is enforced through stand-alone
|
|
Perform |
|
Perform |
|
Perform |
|
Perform both |
|
Enforce sequential consistency. Implies |
For backward compatibility with code that was written for compilers that lacked support for C++11 scoped enums, the library also defines unscoped synonyms:
C++11 constant |
Pre-C++11 constant |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
New code should prefer the C++11 spelling.
See section happens-before for explanation of the various ordering constraints.
#include <boost/atomic/atomic_flag.hpp>
The boost::atomic_flag
type provides the most basic
set of atomic operations suitable for implementing mutually exclusive access
to thread-shared data. The flag can have one of the two possible states:
set and clear. The class implements the following operations:
Syntax |
Description |
---|---|
|
Initialize to the clear state. See the discussion below. |
|
Checks if the atomic flag is lock-free; the returned value is consistent
with the |
|
Indicates if the target platform natively supports waiting and
notifying operations for this object. Returns |
|
Returns |
|
Sets the atomic flag to the set state; returns |
|
Sets the atomic flag to the clear state. |
|
Potentially blocks the calling thread until unblocked by a notifying
operation and |
|
Potentially blocks the calling thread until either unblocked by
a notifying operation and |
|
Potentially blocks the calling thread until either unblocked by
a notifying operation and |
|
Unblocks at least one thread blocked in a waiting operation on this atomic object. |
|
Unblocks all threads blocked in waiting operations on this atomic object. |
|
This static boolean constant indicates if any atomic flag is lock-free |
|
Indicates if the target platform always natively supports waiting and notifying operations. |
order
always has memory_order::seq_cst
as default parameter.
Waiting and notifying operations are described in detail in this section.
Note that the default constructor atomic_flag()
is unlike C++11 std::atomic_flag
,
which leaves the default-constructed object uninitialized. C++20 changes
std::atomic_flag
default constructor to initialize
the flag to the clear state, similar to Boost.Atomic.
This potentially requires dynamic initialization during the program startup
to perform the object initialization, which makes it unsafe to create global
boost::atomic_flag
objects that can be used before
entring main()
.
Some compilers though (especially those supporting C++11 constexpr
)
may be smart enough to perform flag initialization statically (which is,
in C++11 terms, a constant initialization).
C++11 defines the ATOMIC_FLAG_INIT
macro which can be used to statically initialize std::atomic_flag
to a clear state like this:
std::atomic_flag flag = ATOMIC_FLAG_INIT; // constant initialization
With Boost.Atomic, the simple declaration
below would have the same effect, if the compiler supports C++11 constexpr
:
boost::atomic_flag flag; // constant initialization
However, for interface parity with std::atomic_flag
,
if possible, the library also defines the BOOST_ATOMIC_FLAG_INIT
macro, which is equivalent to ATOMIC_FLAG_INIT
:
boost::atomic_flag flag = BOOST_ATOMIC_FLAG_INIT; // constant initialization
#include <boost/atomic/atomic.hpp>
boost::atomic<T>
provides methods
for atomically accessing variables of a suitable type T
.
The type is suitable if it is trivially
copyable (3.9/9 [basic.types]). Following are examples
of the types compatible with this requirement:
class
or struct
that has no non-trivial
copy or move constructors or assignment operators, has a trivial destructor,
and that is comparable via memcmp
while disregarding
any padding bits (but see below).
Note that classes with virtual functions or virtual base classes do not satisfy the requirements.
Also be warned that the support for types with padding bits is largely dependent
on compiler offering a way to set the padding bits to a known state (e.g.
zero). Such feature is typically present in compilers supporting C++20. When
this feature is not supported by the compiler, BOOST_ATOMIC_NO_CLEAR_PADDING
capability macro is defined and types with padding bits may compare non-equal
via memcmp
even though all members are equal. This may
also be the case with some floating point types, which include padding bits
themselves. In this case, Boost.Atomic attempts
to support some floating point types where the location of the padding bits
is known (one notable example is 80-bit
extended precision long double
type on x86 targets), but otherwise
types with padding bits are not supported.
![]() |
Note |
---|---|
Even on compilers that support clearing the padding bits, unions with padding
may not work as expected. Compiler behavior varies with respect to unions.
In particular, gcc 11 clears bytes that constitute padding across all union
members (which is what is required by C++20 in [atomics.types.operations]/28)
and MSVC 19.27 does
not clear any padding at all. Also, consider that some bits of
the union representation may constitute padding in one member of the union
but contribute to value of another. Current compilers cannot reliably track
the active member of a union and therefore cannot implement a reasonable
behavior with respect to clearing those bits. As a result, padding bits
of the currently active union member may be left uninitialized, which will
prevent atomic operations from working reliably. The C++20 standard explicitly
allows |
All atomic objects support the following operations and properties:
Syntax |
Description |
---|---|
|
Initialize to a value of |
|
Initialize to |
|
Checks if the atomic object is lock-free; the returned value
is consistent with the |
|
Indicates if the target platform natively supports waiting and
notifying operations for this object. Returns |
|
Returns a reference to the value stored in the atomic object. |
|
Return current value |
|
Write new value to atomic variable |
|
Exchange current value with |
|
Compare current value with |
|
Compare current value with |
|
Compare current value with |
|
Compare current value with |
|
Potentially blocks the calling thread until unblocked by a notifying
operation and |
|
Potentially blocks the calling thread until either unblocked
by a notifying operation and |
|
Potentially blocks the calling thread until either unblocked
by a notifying operation and |
|
Unblocks at least one thread blocked in a waiting operation on this atomic object. |
|
Unblocks all threads blocked in waiting operations on this atomic object. |
|
This static boolean constant indicates if any atomic object of this type is lock-free |
|
Indicates if the target platform always natively supports waiting and notifying operations. |
order
always has memory_order::seq_cst
as default parameter.
The default constructor of boost::atomic<T>
is different from C++11 std::atomic<T>
and is in line with C++20. In C++11 (and older Boost.Atomic
releases), the default constructor performed default initialization of
the contained object of type T
,
which results in unspecified value if T
does not have a user-defined constructor. C++20 and the current Boost.Atomic version performs value initialization,
which means zero initialization in this case.
Waiting and notifying operations are described in detail in this section.
The value
operation is
a Boost.Atomic extension. The returned
reference can be used to invoke external operations on the atomic value,
which are not part of Boost.Atomic but
are compatible with it on the target architecture. The primary example
of such is futex
and similar
operations available on some systems. The returned reference must not be
used for reading or modifying the value of the atomic object in non-atomic
manner, or to construct atomic
references. Doing so does not guarantee atomicity or memory ordering.
![]() |
Note |
---|---|
Even if |
The compare_exchange_weak
/compare_exchange_strong
variants taking
four parameters differ from the three parameter variants in that they allow
a different memory ordering constraint to be specified in case the operation
fails.
It must be noted that compare_exchange_weak
/compare_exchange_strong
in Boost.Atomic differ from C++11 std::atomic<T>
in that boost::atomic<T>
is allowed to write to the expected
argument even if the operation returns true
while std::atomic<T>
writes
to that argument only if the operation returns false
.
The difference may be significant if the caller passes in the expected
argument a reference to data
that must be protected by the compare_exchange_*
operation, because the write to expected
may happen after the atomic
update with the success_order
constraint and constitute a data race. Users are advised to avoid passing
references to protected data as expected
arguments.
In addition to these explicit operations, each atomic<T>
object also supports implicit store
and load
through the use of "assignment" and "conversion to T
"
operators. Avoid using these operators, as they do not allow to specify
a memory ordering constraint which always defaults to memory_order::seq_cst
.
In addition to the operations listed in the previous section, boost::atomic<I>
for integral types I
, except bool
, supports the following operations,
which correspond to std::atomic<I>
:
Syntax |
Description |
---|---|
|
Add |
|
Subtract |
|
Apply bit-wise "and" with |
|
Apply bit-wise "or" with |
|
Apply bit-wise "xor" with |
Additionally, as a Boost.Atomic extension, the following operations are also provided:
Syntax |
Description |
---|---|
|
Change the sign of the value stored in the variable, returning previous value |
|
Set the variable to the one's complement of the current value, returning previous value |
|
Change the sign of the value stored in the variable, returning the result |
|
Add |
|
Subtract |
|
Apply bit-wise "and" with |
|
Apply bit-wise "or" with |
|
Apply bit-wise "xor" with |
|
Set the variable to the one's complement of the current value, returning the result |
|
Change the sign of the value stored in the variable, returning nothing |
|
Add |
|
Subtract |
|
Apply bit-wise "and" with |
|
Apply bit-wise "or" with |
|
Apply bit-wise "xor" with |
|
Set the variable to the one's complement of the current value, returning nothing |
|
Change the sign of the value stored in the variable, returning
|
|
Add |
|
Subtract |
|
Apply bit-wise "and" with |
|
Apply bit-wise "or" with |
|
Apply bit-wise "xor" with |
|
Set the variable to the one's complement of the current value,
returning |
|
Set bit number |
|
Set bit number |
|
Change bit number |
![]() |
Note |
---|---|
In Boost.Atomic 1.66 the |
order
always has memory_order::seq_cst
as default parameter.
The opaque_op
and op_and_test
variants of the operations may result in a more efficient code on some
architectures because the original value of the atomic variable is not
preserved. In the bit_test_and_op
operations, the bit number n
starts from 0, which means the least significand bit, and must not exceed
std::numeric_limits<I>::digits - 1
.
In addition to these explicit operations, each boost::atomic<I>
object also supports implicit pre-/post- increment/decrement, as well as
the operators +=
, -=
, &=
,
|=
and ^=
.
Avoid using these operators, as they do not allow to specify a memory ordering
constraint which always defaults to memory_order::seq_cst
.
In addition to the operations listed in the common
template, boost::atomic<E>
for enumeration types E
, supports
the following operations:
Syntax |
Description |
---|---|
|
Apply bit-wise "and" with |
|
Apply bit-wise "or" with |
|
Apply bit-wise "xor" with |
|
Set the variable to the one's complement of the current value, returning previous value |
|
Apply bit-wise "and" with |
|
Apply bit-wise "or" with |
|
Apply bit-wise "xor" with |
|
Set the variable to the one's complement of the current value, returning the result |
|
Apply bit-wise "and" with |
|
Apply bit-wise "or" with |
|
Apply bit-wise "xor" with |
|
Set the variable to the one's complement of the current value, returning nothing |
|
Apply bit-wise "and" with |
|
Apply bit-wise "or" with |
|
Apply bit-wise "xor" with |
|
Set the variable to the one's complement of the current value,
returning |
|
Set bit number |
|
Set bit number |
|
Change bit number |
All operations listed above are Boost.Atomic
extensions. order
always
has memory_order::seq_cst
as default parameter.
![]() |
Tip |
---|---|
Bitwise operations can be useful if the enumeration is used to implement a bit mask or a set of flags. |
The effect of the atomic operations is as if the input values of the enumeration type are converted to the underlying type of the enumeration, then the operation is performed on the values of the underlying type, then, where applicable, the result of the operation is converted back to the enumeration type.
![]() |
Warning |
---|---|
Formally, some of these operations may produce values outside the range
of valid values of the enumeration, as defined by the C++ standard. Specifically,
for enumerations whose underlying type is not fixed (i.e. unscoped |
The above means no user-defined operators will be called to implement the
atomic operations. This is similar to the compare_exchange_weak
/compare_exchange_strong
operations, which
also do not invoke any user-defined operator==
to compare the values. If user-defined
operators are defined for the enumeration, their behavior may differ from
the atomic operations. If the behavior of the user-defined operators is
preferable, it can be achieved with a compare-exchange loop.
enum class flags : std::uint32_t { none = 0u, one = 1u, two = 1u << 1u, four = 1u << 2u, all = (1u << 3u) - 1u }; flags operator~ (flags value) { // Only invert bits that are named in the enum return static_cast<flags>(static_cast<std::uint32_t>(value) ^ static_cast<std::uint32_t>(flags::all)); } atomic<flags> atomic_flags(flags::none); // bitwise_complement() operates on the value of the underlying type assert(atomic_flags.bitwise_complement() == (flags)0xFFFFFFFF); atomic_flags.store(flags::none); // Enforce behavior of the user-defined operator~ { flags old_val = atomic_flags.load(), new_val; do { new_val = ~old_val; } while (!atomic_flags.compare_exchange_weak(old_val, new_val)); assert(new_val == flags::all); } atomic_flags.store(flags::none); // Achieve the behavior similar to the user-defined operator~ through other atomic operations assert(atomic_flags.bitwise_xor(flags::all) == flags::all);
The opaque_op
and op_and_test
variants of the operations may result in a more efficient code on some
architectures because the original value of the atomic variable is not
preserved. In the bit_test_and_op
operations, the bit number n
starts from 0, which means the least significand bit, and must not exceed
std::numeric_limits<std::underlying_type<E>::type>::digits
- 1
for enumerations with fixed underlying type or the index
of the most significant bit that contributes to the value for enumerations
with non-fixed underlying type.
In addition to these explicit operations, each boost::atomic<E>
object also supports the operators &=
,
|=
and ^=
.
Avoid using these operators, as they do not allow to specify a memory ordering
constraint which always defaults to memory_order::seq_cst
.
![]() |
Note |
---|---|
The support for floating point types is optional and can be disabled
by defining |
In addition to the operations applicable to all atomic objects, boost::atomic<F>
for floating point types F
supports
the following operations, which correspond to std::atomic<F>
:
Syntax |
Description |
---|---|
|
Add |
|
Subtract |
Additionally, as a Boost.Atomic extension, the following operations are also provided:
Syntax |
Description |
---|---|
|
Change the sign of the value stored in the variable, returning previous value |
|
Change the sign of the value stored in the variable, returning the result |
|
Add |
|
Subtract |
|
Change the sign of the value stored in the variable, returning nothing |
|
Add |
|
Subtract |
order
always has memory_order::seq_cst
as default parameter.
The opaque_op
variants of the operations
may result in a more efficient code on some architectures because the original
value of the atomic variable is not preserved.
In addition to these explicit operations, each boost::atomic<F>
object also supports operators +=
and -=
. Avoid using these
operators, as they do not allow to specify a memory ordering constraint
which always defaults to memory_order::seq_cst
.
When using atomic operations with floating point types, bear in mind that
Boost.Atomic always performs bitwise comparison
of the stored values. This means that operations like compare_exchange*
may fail if the stored value and comparand
have different binary representation, even if they would normally compare
equal. This is typically the case when either of the numbers is denormalized.
This also means that the behavior with regard to special floating point
values like NaN and signed zero is also different from normal C++.
Another source of the problem may be the padding bits that are added to
some floating point types for alignment. One widespread example of that
is Intel x87 80-bit
extended precision long
double
format, which is typically
stored as 80 bits of value padded with 16 or 48 unused bits. These padding
bits are often uninitialized and contain garbage, which makes two equal
numbers have different binary representation. This problem is solved if
the compiler provides a way to reliably clear the padding bits before operation.
Otherwise, the library attempts to account for the known such cases, but
in general it is possible that some platforms are not covered. The library
defines BOOST_ATOMIC_NO_CLEAR_PADDING
capability macro to indicate that general support for types with padding
bits is not available.
In addition to the operations applicable to all atomic objects, boost::atomic<P>
for pointer types P
(other than
pointers to void
, function or member pointers) support
the following operations, which correspond to std::atomic<P>
:
Syntax |
Description |
---|---|
|
Add |
|
Subtract |
Similarly to integers, the following Boost.Atomic extensions are also provided:
Syntax |
Description |
---|---|
|
Add |
|
Subtract |
|
Add |
|
Subtract |
|
Add |
|
Subtract |
order
always has memory_order::seq_cst
as default parameter.
In addition to these explicit operations, each boost::atomic<P>
object also supports implicit pre-/post- increment/decrement, as well as
the operators +=
, -=
. Avoid using these operators, as they
do not allow explicit specification of a memory ordering constraint which
always defaults to memory_order::seq_cst
.
For convenience, the following shorthand type aliases of boost::atomic<T>
are provided:
using atomic_char = atomic<char>; using atomic_uchar = atomic<unsigned char>; using atomic_schar = atomic<signed char>; using atomic_ushort = atomic<unsigned short>; using atomic_short = atomic<short>; using atomic_uint = atomic<unsigned int>; using atomic_int = atomic<int>; using atomic_ulong = atomic<unsigned long>; using atomic_long = atomic<long>; using atomic_ullong = atomic<unsigned long long>; using atomic_llong = atomic<long long>; using atomic_address = atomic<void*>; using atomic_bool = atomic<bool>; using atomic_wchar_t = atomic<wchar_t>; using atomic_char8_t = atomic<char8_t>; using atomic_char16_t = atomic<char16_t>; using atomic_char32_t = atomic<char32_t>; using atomic_uint8_t = atomic<std::uint8_t>; using atomic_int8_t = atomic<std::int8_t>; using atomic_uint16_t = atomic<std::uint16_t>; using atomic_int16_t = atomic<std::int16_t>; using atomic_uint32_t = atomic<std::uint32_t>; using atomic_int32_t = atomic<std::int32_t>; using atomic_uint64_t = atomic<std::uint64_t>; using atomic_int64_t = atomic<std::int64_t>; using atomic_int_least8_t = atomic<std::int_least8_t>; using atomic_uint_least8_t = atomic<std::uint_least8_t>; using atomic_int_least16_t = atomic<std::int_least16_t>; using atomic_uint_least16_t = atomic<std::uint_least16_t>; using atomic_int_least32_t = atomic<std::int_least32_t>; using atomic_uint_least32_t = atomic<std::uint_least32_t>; using atomic_int_least64_t = atomic<std::int_least64_t>; using atomic_uint_least64_t = atomic<std::uint_least64_t>; using atomic_int_fast8_t = atomic<std::int_fast8_t>; using atomic_uint_fast8_t = atomic<std::uint_fast8_t>; using atomic_int_fast16_t = atomic<std::int_fast16_t>; using atomic_uint_fast16_t = atomic<std::uint_fast16_t>; using atomic_int_fast32_t = atomic<std::int_fast32_t>; using atomic_uint_fast32_t = atomic<std::uint_fast32_t>; using atomic_int_fast64_t = atomic<std::int_fast64_t>; using atomic_uint_fast64_t = atomic<std::uint_fast64_t>; using atomic_intmax_t = atomic<std::intmax_t>; using atomic_uintmax_t = atomic<std::uintmax_t>; using atomic_size_t = atomic<std::size_t>; using atomic_ptrdiff_t = atomic<std::ptrdiff_t>; using atomic_intptr_t = atomic<std::intptr_t>; using atomic_uintptr_t = atomic<std::uintptr_t>; using atomic_unsigned_lock_free = atomic<unsigned integral>; using atomic_signed_lock_free = atomic<signed integral>;
The type aliases are provided only if the corresponding value type is available.
The atomic_unsigned_lock_free
and atomic_signed_lock_free
types, if defined, indicate the atomic object type for an unsigned or signed
integer, respectively, that is lock-free and that preferably has native
support for waiting
and notifying operations.
#include <boost/atomic/atomic_ref.hpp>
boost::atomic_ref<T>
also provides
methods for atomically accessing external variables of type T
.
The requirements on the type T
are
the same as those imposed by boost::atomic
. Unlike boost::atomic
,
boost::atomic_ref
does not store the value internally
and only refers to an external object of type T
.
There are certain requirements on the objects compatible with boost::atomic_ref
:
boost::atomic_ref
referencing the object is
destroyed.
boost::atomic_ref<T>::required_alignment
constant. That constant may be larger than the natural alignment of type
T
. In Boost.Atomic,
required_alignment
indicates
the alignment at which operations on the object are lock-free; otherwise,
if lock-free operations are not possible, required_alignment
shall not be less than the natural alignment of T
.
[[no_unique_address]]
attribute.
struct Base { short a; char b; }; struct Derived : public Base { char c; }; Derived x; boost::atomic_ref<Base> ref(x); // badIn the above example,
ref
may silently corrupt the value of x.c
because it may reside in the trailing padding of the Base
base class subobject of x
.
load()
, boost::atomic_ref
may issue read-modify-write CPU instructions that require write access.
boost::atomic_ref
referencing an object exists, that object must not be accessed by any
other means, other than through boost::atomic_ref
.
Multiple boost::atomic_ref
referencing the same object
are allowed, and operations through any such reference are atomic and ordered
with regard to each other, according to the memory order arguments. boost::atomic_ref<T>
supports the same set of properties and operations as boost::atomic<T>
,
depending on the type T
, with the
following exceptions:
Syntax |
Description |
---|---|
|
|
|
Creates an atomic reference, referring to |
|
Creates an atomic reference, referencing the object referred to
by |
|
A constant, indicating required alignment of objects of type |
Note that boost::atomic_ref
cannot be changed to refer to
a different object after construction. Assigning to boost::atomic_ref
will invoke an atomic operation of storing the new value to the referenced
object.
For convenience, a factory function make_atomic_ref(T&
object)
is provided, which returns an atomic_ref<T>
referencing object
. Additionally,
for C++17 and later compilers, template deduction guides are provided so
that the template parameter T can be deduced from the
constructor argument:
int object = 0; atomic_ref ref(object); // C++17: ref is atomic_ref<int>
There are a several disadvantages of using boost::atomic_ref
compared to boost::atomic
.
First, the user is required to maintain proper alignment of the referenced
objects. This means that the user has to plan beforehand which variables
will require atomic access in the program. In C++11 and later, the user
can ensure the required alignment by applying alignas
specifier:
alignas(boost::atomic_ref<int>::required_alignment) int atomic_int;
On compilers that don't support alignas
users have to use compiler-specific attributes or manual padding to achieve
the required alignment. BOOST_ALIGNMENT
macro from Boost.Config may be useful.
![]() |
Note |
---|---|
Do not rely on compilers to enforce the natural alignment for fundamental
types, and that the default alignment will satisfy the |
Next, some types may have padding bits, which are bits of object representation
that do not contribute to the object value. Typically, padding bits are
used for alignment purposes. Padding bits pose a problem for Boost.Atomic because they can break binary comparison
of object (as if by memcmp
),
which is used in compare_exchange_weak
/compare_exchange_strong
operations.
boost::atomic
manages the internal object representation
and, with proper support of the compiler, it is able to initialize the
padding bits so that binary comparison yields the expected result. This
is not possible with boost::atomic_ref
because the referenced object is initialized by external means and any
particular content in the padding bits cannot be guaranteed. This requires
boost::atomic_ref
to initialize padding bits
of the referenced object on construction. As a result, boost::atomic_ref
construction can be relatively expensive and may potentially disrupt atomic
operations that are being performed on the same object through other atomic
references. It is recommended to avoid constructing boost::atomic_ref
in tight loops or hot paths.
Finally, target platform may not have the necessary means to implement atomic operations on objects of some sizes. For example, on many hardware architectures atomic operations on the following structure are not possible:
struct rgb { unsigned char r, g, b; // 3 bytes };
boost::atomic<rgb>
is able to implement lock-free operations if the target CPU supports 32-bit
atomic instructions by padding rgb
structure internally to the size of 4 bytes. This is not possible for
boost::atomic_ref<rgb>
,
as it has to operate on external objects. Thus, boost::atomic_ref<rgb>
will not provide lock-free operations
and will resort to locking.
In general, it is advised to use boost::atomic
wherever possible, as it is easier to use and is more efficient. Use boost::atomic_ref
only when you absolutely have
to.
boost::atomic_flag
, boost::atomic<T>
and boost::atomic_ref<T>
support
waiting and notifying operations
that were introduced in C++20. Waiting operations have the following forms:
T wait(T old_val,
memory_order order)
template<typename Clock, typename Duration> wait_result<T>
wait_until(T old_val, std::chrono::time_point<Clock,
Duration> timeout, memory_order order)
template<typename Rep, typename Period> wait_result<T>
wait_for(T old_val, std::chrono::duration<Rep,
Period> timeout, memory_order order)
For boost::atomic_flag
, T is
bool
. Here, order
must not be memory_order::release
or memory_order::acq_rel
. Note that unlike C++20, the wait
operation returns T
instead of void
. This is a
Boost.Atomic extension. wait_until
and wait_for
are timed
waiting operations and are also Boost.Atomic
extensions.
The waiting operations perform the following steps repeatedly:
new_val
of the atomic object using the memory ordering constraint order
. For boost::atomic_flag
,
the load is performed as if by test(order)
, for other atomic objects - as if by
load(order)
.
new_val
representation
is different from old_val
(i.e. when compared as if by memcmp
),
returns. For wait
, the
returned value is new_val
.
For timed waiting operations, the returned value is a wait_result<T>
object r
, where r.value
is new_val
and t.timeout
is contextually convertible
to false
.
timeout
has expired, returns a wait_result<T>
object r
, where r.value
is new_val
and t.timeout
is contextually convertible
to true
. wait_for
tracks timeout
against
an unspecified steady clock.
timeout
expires.
Note that a waiting operation is allowed to return spuriously, i.e. without
a corresponding notifying operation. It is also allowed to not
return if the atomic object value is different from old_val
only momentarily (this is known as ABA
problem). For timed waiting operations, the precision of tracking
the timeout is dependent on hardware and the underlying operating system
and may be different for different clock types. For clocks that support time
adjustments, it is unspecified whether the adjustments unblock the waiting
threads (for example, if the clock is adjusted forward while a thread is
waiting, the thread may not get unblocked until after the timeout expires
as if no adjustment happened).
Notifying operations have the following forms:
void notify_one()
void notify_all()
The notify_one
operation
unblocks at least one thread blocked in the waiting operation on the same
atomic object, and notify_all
unblocks all such threads. Notifying operations do not enforce memory ordering
and should normally be preceeded with a store operation or a fence with the
appropriate memory ordering constraint.
Waiting and notifying operations require special support from the operating
system, which may not be universally available. Whether the operating system
natively supports these operations is indicated by the always_has_native_wait_notify
static constant and has_native_wait_notify()
member function of a given atomic type.
Even for atomic objects that support lock-free operations (as indicated by
the is_always_lock_free
property
or the corresponding macro),
the waiting and notifying operations may involve locking and require linking
with Boost.Atomic compiled library.
Waiting and notifying operations are not address-free, meaning that the implementation may use process-local state and process-local addresses of the atomic objects to implement the operations. In particular, this means these operations cannot be used for communication between processes (when the atomic object is located in shared memory) or when the atomic object is mapped at different memory addresses in the same process.
Although timed waiting operations will work by default for any clock types
(subject to the caveats outlined in the previous
section), the implementation will track the timout against one of the known
and available clock types internally, which on POSIX systems is typically
CLOCK_MONOTONIC
or CLOCK_REALTIME
. Coordinating between
user-specified and internal clocks incurs performance overhead, as the
waiting operation will have to query clock timestamps and may perform multiple
blocking operations and wake ups as it tries to exhaust the alotted timeout.
Users may improve performance when they use custom clock types that map
onto one of the POSIX clocks (see e.g. clock_gettime
) that are supported
by the operating system for blocking timeouts by providing a specialization
of the posix_clock_traits
class template as follows:
#include <boost/atomic/posix_clock_traits_fwd.hpp> namespace boost { namespace atomics { template< > struct posix_clock_traits<my_clock> { // POSIX clock identifier (e.g. CLOCK_MONOTONIC) onto which my_clock maps static constexpr clockid_t clock_id = ...; // Function that converts a my_clock time point to a POSIX timespec structure that corresponds to clock_id static timespec to_timespec(my_clock::time_point time_point) noexcept; }; } // namespace atomics } // namespace boost
Here, my_clock
is the user-defined
clock type that satisfies the C++11 chrono clock requirements (20.11.3,
[time.clock.req]). Note that posix_clock_traits
must be specialized in namespace boost::atomics
.
The to_timespec
function
must be able to convert any valid time point to the timespec
structure, and therefore must not throw.
When this specialization is provided, and posix_clock_traits<my_clock>::clock_id
is supported by the underlying OS, the waiting operation will be able to
convert the passed my_clock
time points to the native format supported by the OS and use them directly.
![]() |
Note |
---|---|
Users need not specialize the |
Advanced users may use the second template argument of posix_clock_traits
,
which is a type that is void
by default, to leverage SFINAE and define partial posix_clock_traits
template specializations that apply to subsets of clock types that satisfy
certain conditions. For example, if there is a number of clock types that
all derive from my_clock_base
,
it is possible to provide a single specialization for them using std::is_base_of
as the limiting criteria.
#include <type_traits> #include <boost/atomic/posix_clock_traits_fwd.hpp> namespace boost { namespace atomics { template<typename MyClock> struct posix_clock_traits<MyClock, typename std::enable_if<std::is_base_of<my_clock_base, MyClock>::value>::type> { // POSIX clock identifier onto which MyClock maps static constexpr clockid_t clock_id = MyClock::clock_id; // Function that converts a MyClock time point to a POSIX timespec structure that corresponds to clock_id static timespec to_timespec(typename MyClock::time_point time_point) noexcept; }; } // namespace atomics } // namespace boost
#include <boost/atomic/ipc_atomic.hpp> #include <boost/atomic/ipc_atomic_ref.hpp> #include <boost/atomic/ipc_atomic_flag.hpp>
Boost.Atomic provides a dedicated set of
types for inter-process communication: boost::ipc_atomic_flag
,
boost::ipc_atomic<T>
and boost::ipc_atomic_ref<T>
.
Collectively, these types are called inter-process communication atomic types
or IPC atomic types, and their counterparts without the ipc_
prefix - non-IPC atomic types.
Each of the IPC atomic types have the same requirements on their value types and provide the same set of operations and properties as its non-IPC counterpart. All operations have the same signature, requirements and effects, with the following amendments:
is_lock_free()
and has_native_wait_notify()
have an additional precondition that
is_lock_free()
returns true
for this atomic
object. (Implementation note: The current implementation detects availability
of atomic instructions at compile time, and the code that does not fulfill
this requirement will fail to compile.)
has_native_wait_notify()
method and always_has_native_wait_notify
static constant indicate whether the operating system has native support
for inter-process waiting and notifying operations. This may be different
from non-IPC atomic types as the OS may have different capabilities for
inter-thread and inter-process communication.
boost::ipc_atomic_ref<T>
- objects referenced by ipc_atomic_ref
)
in memory regions shared between processes or mapped at different addresses
in the same process.
![]() |
Note |
---|---|
Operations on lock-free non-IPC atomic objects, except waiting
and notifying operations, are also address-free, so |
It should be noted that some operations on IPC atomic types may be more expensive
than the non-IPC ones. This primarily concerns waiting and notifying operations,
as the operating system may have to perform conversion of the process-mapped
addresses of atomic objects to physical addresses. Also, when native support
for inter-process waiting and notifying operations is not present (as indicated
by has_native_wait_notify()
), waiting operations are emulated with
a busy loop, which can affect performance and power consumption of the system.
Native support for waiting and notifying operations can also be detected
using capability macros.
Users must not create and use IPC and non-IPC atomic references on the same referenced object at the same time. IPC and non-IPC atomic references are not required to communicate with each other. For example, a waiting operation on a non-IPC atomic reference may not be interrupted by a notifying operation on an IPC atomic reference referencing the same object.
Additionally, users must not create IPC atomics on the stack and, possibly, other non-shared memory. Waiting and notifying operations may not behave as intended on some systems if the atomic object is placed in an unsupported memory type. For example, on Mac OS notifying operations are known to fail spuriously if the IPC atomic is on the stack. Use regular atomic objects in process-local memory. Users should also avoid modifying properties of the memory while IPC atomic operations are running. For example, resizing the shared memory segment while threads are blocked on a waiting operation may prevent subsequent notifying operations from waking up the blocked threads.
#include <boost/atomic/fences.hpp>
Fences are implemented with the following operations:
Syntax |
Description |
---|---|
|
Issue fence for coordination with other threads. |
|
Issue fence for coordination with a signal handler (only in the same thread). |
Note that atomic_signal_fence
does not implement thread synchronization and only acts as a barrier to prevent
code reordering by the compiler (but not by CPU). The order
argument here specifies the direction, in which the fence prevents the compiler
to reorder code.
#include <boost/atomic/thread_pause.hpp>
Atomic operations are often used in tight loops, also called spin loops,
where the same sequence of operations is attempted repeatedly until the atomic
object is successfully updated. On modern processors with SMT
such tight loops may result in suboptimal performance as they may be utilizing
CPU core resources that could otherwise be used by a sibling thread of the
same core. The thread_pause
operation that is described in this section may help in this case:
Syntax |
Description |
---|---|
|
Hint the CPU core to reallocate hardware resources to favor other sibling threads it is currently running. |
The intended use case of thread_pause
is to call it within a spin loop, after a failed iteration, as a backoff
measure. The effects of this hint are CPU architecture dependent and may
vary between CPU models and manufacturers. It may also be a no-op. When not
a no-op, it will typically suspend the calling therad execution for a number
of CPU clock cycles, giving the sibling threads the opportunity to use the
freed hardware resources to progress further and possibly allow the calling
thread to succeed on the next spin loop iteration. From the operating system
perspective, thread_pause
does not block the thread.
#include <boost/atomic/capabilities.hpp>
Boost.Atomic defines a number of macros
to allow compile-time detection whether an atomic data type is implemented
using "true" atomic operations, or whether an internal "lock"
is used to provide atomicity. The following macros will be defined to 0
if operations on the data type always require
a lock, to 1
if operations on
the data type may sometimes require a lock, and to 2
if they are always lock-free:
Macro |
Description |
---|---|
|
Indicate whether |
|
Indicate whether |
|
Indicate whether |
|
Indicate whether |
|
Indicate whether |
|
Indicate whether |
|
Indicate whether |
|
Indicate whether |
|
Indicate whether |
|
Indicate whether |
|
Indicate whether |
|
Indicate whether |
|
Indicate whether |
|
Indicate whether |
In addition to these standard macros, Boost.Atomic
also defines a number of extension macros, which can also be useful. Like
the standard ones, the *_LOCK_FREE
macros below are defined to values 0
,
1
and 2
to indicate whether the corresponding operations are lock-free or not.
Macro |
Description |
---|---|
|
Indicate whether |
|
Indicate whether |
|
Indicate whether |
|
Indicate whether |
|
Indicate whether |
|
Defined if the implementation does not support operating on types with internal padding bits. This macro is typically defined for compilers that don't support C++20. |
In the table above, intN_type
is
a type that fits storage of contiguous N bits, suitably
aligned for atomic operations.
For floating-point types the following macros are similarly defined:
Macro |
Description |
---|---|
|
Indicate whether |
|
Indicate whether |
|
Indicate whether |
These macros are not defined when support for floating point types is disabled by user.
For any of the BOOST_ATOMIC_X_LOCK_FREE
macro described above, two additional macros named BOOST_ATOMIC_HAS_NATIVE_X_WAIT_NOTIFY
and BOOST_ATOMIC_HAS_NATIVE_X_IPC_WAIT_NOTIFY
are defined. The former indicates whether waiting
and notifying operations are supported natively for non-IPC atomic
types of a given type, and the latter does the same for IPC
atomic types. The macros take values of 0
,
1
or 2
,
where 0
indicates that native
operations are not available, 1
means the operations may be available (which is determined at run time) and
2
means always available. Note
that the lock-free and native waiting/notifying operations macros for a given
type may have different values.