XLE  v0.02.0
Typedefs | Functions
Utility::Interlocked Namespace Reference

Typedefs

typedef long Value
 
typedef int64 Value64
 

Functions

force_inline Value Exchange (Value volatile *target, Value newValue)
 
Value CompareExchange (Value volatile *target, Value newValue, Value comparisonValue)
 
force_inline Value64 CompareExchange64 (Value64 volatile *target, Value64 newValue, Value64 comparisonValue)
 
force_inline Value64 Exchange64 (Value64 volatile *target, Value64 newValue)
 
force_inline void * ExchangePointer (void *volatile *target, void *newValue)
 
force_inline void * CompareExchangePointer (void *volatile *target, void *newValue, void *comparisonValue)
 
force_inline Value Increment (Value volatile *target)
 
force_inline Value Decrement (Value volatile *target)
 
force_inline Value Add (Value volatile *target, Value addition)
 
force_inline Value Load (Value volatile *target)
 
force_inline Value64 Load64 (Value64 volatile const *target)
 
force_inline void * LoadPointer (void *volatile const *target)
 

Detailed Description

This namespace provides low-level utility functions for atomic operations. Mostly this namespace wraps some low-level api providing the functionality required (this gives us flexibility to choose different low level api, and perhaps provide different solutions for different platforms).

Return values

Important note: Functions in the Interlocked namespace always return the previous value of the target! This differs from the Win32 API InterlockedAdd, etc!

Win32 InterlockedAdd(&target, addition) -> returns old "target" + addition
Interlocked::InterlockedAdd(&target, addition) -> returns old "target"

This simple rule seems is more intuitive and consistant. Interlocked::Exchange and other functions also work in this same way – return the "old" value.

But it means that code previously used Win32 InterlockedAdd() must be change to suit this behaviour.

As an example, here is reference count Release() behaviour:

@code
auto oldRefCount = Interlocked::Decrement(&refCount);
if (oldRefCount == 1) {
// (ref count was previously 1, and so is now 0)
delete *this;
}
\endcode

Release / Acquire semantics

When using atomic functions, there are issues related to the ordering of instruction completion relative to other instructions. In certain cases, a thread can see an atomic value change in way that appears out-of-order relative to other memory changes.

This is particularly important for mutexes and threading control primitives. When a mutex is unlocked, it's normal to assume that memory writes to the object that mutex protects will be committed to memory. However, this isn't guaranteed unless there is a memory barrier to properly synchronize memory. Normally mutex implementations will take care of this. However it's worth noting when writing custom thread control (eg, a spin lock) that use these Interlocked methods.

For more information on this topic, see documentation on std::memory_order (part of the new C++ standard)

When using the Win32 intrinsic implementations of InterlockedExchange, etc, these automatically place a read/write barrier that effects the behaviour of the compiler, and possibly a memory barrier as well. See the MSVC documentation for _ReadWriteBarrier. The documentation is a little unclear about whether there is a runtime memory barrier (and on what platforms it takes effect).