313 lines
11 KiB
Plaintext
313 lines
11 KiB
Plaintext
[/
|
|
/ Copyright (c) 2009 Helge Bahmann
|
|
/
|
|
/ Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
/ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
/]
|
|
|
|
[section:template_organization Organization of class template layers]
|
|
|
|
The implementation uses multiple layers of template classes that
|
|
inherit from the next lower level each and refine or adapt the respective
|
|
underlying class:
|
|
|
|
* [^boost::atomic<T>] is the topmost-level, providing
|
|
the external interface. Implementation-wise, it does not add anything
|
|
(except for hiding copy constructor and assignment operator).
|
|
|
|
* [^boost::detail::atomic::internal_atomic&<T,S=sizeof(T),I=is_integral_type<T> >]:
|
|
This layer is mainly responsible for providing the overloaded operators
|
|
mapping to API member functions (e.g. [^+=] to [^fetch_add]).
|
|
The defaulted template parameter [^I] allows
|
|
to expose the correct API functions (via partial template
|
|
specialization): For non-integral types, it only
|
|
publishes the various [^exchange] functions
|
|
as well as load and store, for integral types it
|
|
additionally exports arithmetic and logic operations.
|
|
[br]
|
|
Depending on whether the given type is integral, it
|
|
inherits from either [^boost::detail::atomic::platform_atomic<T,S=sizeof(T)>]
|
|
or [^boost::detail::atomic::platform_atomic_integral<T,S=sizeof(T)>].
|
|
There is however some special-casing: for non-integral types
|
|
of size 1, 2, 4 or 8, it will coerce the datatype into an integer representation
|
|
and delegate to [^boost::detail::atomic::platform_atomic_integral<T,S=sizeof(T)>]
|
|
-- the rationale is that platform implementors only need to provide
|
|
integer-type operations.
|
|
|
|
* [^boost::detail::atomic::platform_atomic_integral<T,S=sizeof(T)>]
|
|
must provide the full set of operations for an integral type T
|
|
(i.e. [^load], [^store], [^exchange],
|
|
[^compare_exchange_weak], [^compare_exchange_strong],
|
|
[^fetch_add], [^fetch_sub], [^fetch_and],
|
|
[^fetch_or], [^fetch_xor], [^is_lock_free]).
|
|
The default implementation uses locking to emulate atomic operations, so
|
|
this is the level at which implementors should provide template specializations
|
|
to add support for platform-specific atomic operations.
|
|
[br]
|
|
The two separate template parameters allow separate specialization
|
|
on size and type (which, with fixed size, cannot
|
|
specify more than signedness/unsignedness). The rationale is that
|
|
most platform-specific atomic operations usually depend only on the
|
|
operand size, so that common implementations for signed/unsigned
|
|
types are possible. Signedness allows to properly to choose sign-extending
|
|
instructions for the [^load] operation, avoiding later
|
|
conversion. The expectation is that in most implementations this will
|
|
be a normal assignment in C, possibly accompanied by memory
|
|
fences, so that the compiler can automatically choose the correct
|
|
instruction.
|
|
|
|
* At the lowest level, [^boost::detail::atomic::platform_atomic<T,S=sizeof(T)>]
|
|
provides the most basic atomic operations ([^load], [^store],
|
|
[^exchange], [^compare_exchange_weak],
|
|
[^compare_exchange_strong]) for arbitrarily generic data types.
|
|
The default implementation uses locking as a fallback mechanism.
|
|
Implementors generally do not have to specialize at this level
|
|
(since these will not be used for the common integral type sizes
|
|
of 1, 2, 4 and 8 bytes), but if s/he can if s/he so wishes to
|
|
provide truly atomic operations for "odd" data type sizes.
|
|
Some amount of care must be taken as the "raw" data type
|
|
passed in from the user through [^boost::atomic<T>]
|
|
is visible here -- it thus needs to be type-punned or otherwise
|
|
manipulated byte-by-byte to avoid using overloaded assignment,
|
|
comparison operators and copy constructors.
|
|
|
|
[endsect]
|
|
|
|
|
|
[section:platform_atomic_implementation Implementing platform-specific atomic operations]
|
|
|
|
In principle implementors are responsible for providing the
|
|
full range of named member functions of an atomic object
|
|
(i.e. [^load], [^store], [^exchange],
|
|
[^compare_exchange_weak], [^compare_exchange_strong],
|
|
[^fetch_add], [^fetch_sub], [^fetch_and],
|
|
[^fetch_or], [^fetch_xor], [^is_lock_free]).
|
|
These must be implemented as partial template specializations for
|
|
[^boost::detail::atomic::platform_atomic_integral<T,S=sizeof(T)>]:
|
|
|
|
[c++]
|
|
|
|
template<typename T>
|
|
class platform_atomic_integral<T, 4>
|
|
{
|
|
public:
|
|
explicit platform_atomic_integral(T v) : i(v) {}
|
|
platform_atomic_integral(void) {}
|
|
|
|
T load(memory_order order=memory_order_seq_cst) const volatile
|
|
{
|
|
// platform-specific code
|
|
}
|
|
void store(T v, memory_order order=memory_order_seq_cst) volatile
|
|
{
|
|
// platform-specific code
|
|
}
|
|
|
|
private:
|
|
volatile T i;
|
|
};
|
|
|
|
As noted above, it will usually suffice to specialize on the second
|
|
template argument, indicating the size of the data type in bytes.
|
|
|
|
[section:automatic_buildup Templates for automatic build-up]
|
|
|
|
Often only a portion of the required operations can be
|
|
usefully mapped to machine instructions. Several helper template
|
|
classes are provided that can automatically synthesize missing methods to
|
|
complete an implementation.
|
|
|
|
At the minimum, an implementor must provide the
|
|
[^load], [^store],
|
|
[^compare_exchange_weak] and
|
|
[^is_lock_free] methods:
|
|
|
|
[c++]
|
|
|
|
template<typename T>
|
|
class my_atomic_32 {
|
|
public:
|
|
my_atomic_32() {}
|
|
my_atomic_32(T initial_value) : value(initial_value) {}
|
|
|
|
T load(memory_order order=memory_order_seq_cst) volatile const
|
|
{
|
|
// platform-specific code
|
|
}
|
|
void store(T new_value, memory_order order=memory_order_seq_cst) volatile
|
|
{
|
|
// platform-specific code
|
|
}
|
|
bool compare_exchange_weak(T &expected, T desired,
|
|
memory_order success_order,
|
|
memory_order_failure_order) volatile
|
|
{
|
|
// platform-specific code
|
|
}
|
|
bool is_lock_free() const volatile {return true;}
|
|
protected:
|
|
// typedef is required for classes inheriting from this
|
|
typedef T integral_type;
|
|
private:
|
|
T value;
|
|
};
|
|
|
|
The template [^boost::detail::atomic::build_atomic_from_minimal]
|
|
can then take care of the rest:
|
|
|
|
[c++]
|
|
|
|
template<typename T>
|
|
class platform_atomic_integral<T, 4>
|
|
: public boost::detail::atomic::build_atomic_from_minimal<my_atomic_32<T> >
|
|
{
|
|
public:
|
|
typedef build_atomic_from_minimal<my_atomic_32<T> > super;
|
|
|
|
explicit platform_atomic_integral(T v) : super(v) {}
|
|
platform_atomic_integral(void) {}
|
|
};
|
|
|
|
There are several helper classes to assist in building "complete"
|
|
atomic implementations from different starting points:
|
|
|
|
* [^build_atomic_from_minimal] requires
|
|
* [^load]
|
|
* [^store]
|
|
* [^compare_exchange_weak] (4-operand version)
|
|
|
|
* [^build_atomic_from_exchange] requires
|
|
* [^load]
|
|
* [^store]
|
|
* [^compare_exchange_weak] (4-operand version)
|
|
* [^compare_exchange_strong] (4-operand version)
|
|
* [^exchange]
|
|
|
|
* [^build_atomic_from_add] requires
|
|
* [^load]
|
|
* [^store]
|
|
* [^compare_exchange_weak] (4-operand version)
|
|
* [^compare_exchange_strong] (4-operand version)
|
|
* [^exchange]
|
|
* [^fetch_add]
|
|
|
|
* [^build_atomic_from_typical] (<I>supported on gcc only</I>) requires
|
|
* [^load]
|
|
* [^store]
|
|
* [^compare_exchange_weak] (4-operand version)
|
|
* [^compare_exchange_strong] (4-operand version)
|
|
* [^exchange]
|
|
* [^fetch_add_var] (protected method)
|
|
* [^fetch_inc] (protected method)
|
|
* [^fetch_dec] (protected method)
|
|
|
|
This will generate a [^fetch_add] method
|
|
that calls [^fetch_inc]/[^fetch_dec]
|
|
when the given parameter is a compile-time constant
|
|
equal to +1 or -1 respectively, and [^fetch_add_var]
|
|
in all other cases. This provides a mechanism for
|
|
optimizing the extremely common case of an atomic
|
|
variable being used as a counter.
|
|
|
|
The prototypes for these methods to be implemented is:
|
|
[c++]
|
|
|
|
template<typename T>
|
|
class my_atomic {
|
|
public:
|
|
T fetch_inc(memory_order order) volatile;
|
|
T fetch_dec(memory_order order) volatile;
|
|
T fetch_add_var(T counter, memory_order order) volatile;
|
|
};
|
|
|
|
These helper templates are defined in [^boost/atomic/detail/builder.hpp].
|
|
|
|
[endsect]
|
|
|
|
[section:automatic_buildup_small Build sub-word-sized atomic data types]
|
|
|
|
There is one other helper template that can build sub-word-sized
|
|
atomic data types even though the underlying architecture allows
|
|
only word-sized atomic operations:
|
|
|
|
[c++]
|
|
|
|
template<typename T>
|
|
class platform_atomic_integral<T, 1> :
|
|
public build_atomic_from_larger_type<my_atomic_32<uint32_t>, T>
|
|
{
|
|
public:
|
|
typedef build_atomic_from_larger_type<my_atomic_32<uint32_t>, T> super;
|
|
|
|
explicit platform_atomic_integral(T v) : super(v) {}
|
|
platform_atomic_integral(void) {}
|
|
};
|
|
|
|
The above would create an atomic data type of 1 byte size, and
|
|
use masking and shifts to map it to 32-bit atomic operations.
|
|
The base type must implement [^load], [^store]
|
|
and [^compare_exchange_weak] for this to work.
|
|
|
|
[endsect]
|
|
|
|
[section:other_sizes Atomic data types for unusual object sizes]
|
|
|
|
In unusual circumstances, an implementor may also opt to specialize
|
|
[^public boost::detail::atomic::platform_atomic<T,S=sizeof(T)>]
|
|
to provide support for atomic objects not fitting an integral size.
|
|
If you do that, keep the following things in mind:
|
|
|
|
* There is no reason to ever do this for object sizes
|
|
of 1, 2, 4 and 8
|
|
* Only the following methods need to be implemented:
|
|
* [^load]
|
|
* [^store]
|
|
* [^compare_exchange_weak] (4-operand version)
|
|
* [^compare_exchange_strong] (4-operand version)
|
|
* [^exchange]
|
|
|
|
The type of the data to be stored in the atomic
|
|
variable (template parameter [^T])
|
|
is exposed to this class, and the type may have
|
|
overloaded assignment and comparison operators --
|
|
using these overloaded operators however will result
|
|
in an error. The implementor is responsible for
|
|
accessing the objects in a way that does not
|
|
invoke either of these operators (using e.g.
|
|
[^memcpy] or type-casts).
|
|
|
|
[endsect]
|
|
|
|
[endsect]
|
|
|
|
[section:platform_atomic_fences Fences]
|
|
|
|
Platform implementors need to provide a function performing
|
|
the action required for [funcref boost::atomic_thread_fence atomic_thread_fence]
|
|
(the fallback implementation will just perform an atomic operation
|
|
on an integer object). This is achieved by specializing the
|
|
[^boost::detail::atomic::platform_atomic_thread_fence] template
|
|
function in the following way:
|
|
|
|
[c++]
|
|
|
|
template<>
|
|
void platform_atomic_thread_fence(memory_order order)
|
|
{
|
|
// platform-specific code here
|
|
}
|
|
|
|
[endsect]
|
|
|
|
[section:platform_atomic_puttogether Putting it altogether]
|
|
|
|
The template specializations should be put into a header file
|
|
in the [^boost/atomic/detail] directory, preferably
|
|
specifying supported compiler and architecture in its name.
|
|
|
|
The file [^boost/atomic/detail/platform.hpp] must
|
|
subsequently be modified to conditionally include the new
|
|
header.
|
|
|
|
[endsect]
|