Final Platform Layer 1.0.0
Loading...
Searching...
No Matches
Atomics

Overview

This section explains what atomics is and how they are used.
For decades CPUs have had built-in hardware instructions for doing thread-safe math and compare operations.
These instructions are available as built-in intrinsics in any modern C/C++ compiler.
In FPL these are called Atomics and are wrapper functions around those intrinsics.

Atomics functions generate memory barriers (or fences) to ensure that memory operations are completed in order.

See Synchronization methods for a comparison with the other synchronization primitives.

Fetch-And-Add (Add)

With a Fetch-And-Add instruction, you can do an atomic addition and get back the previous value (before the add) as an atomic operation.

Pseudo code:

oldValue = *storageValue;
*storageValue = oldValue + inc;
return oldValue;

FPL has several versions for various datatypes:

Usage:

volatile uint32_t storageValue;
uint32_t oldValue = fplAtomicFetchAndAddU32(&storageValue, 7);
fpl_platform_api uint32_t fplAtomicFetchAndAddU32(volatile uint32_t *value, const uint32_t addend)
Adds a 32-bit unsigned integer to the value by the given addend atomically.

Add-And-Fetch (Increment)

With an Add-And-Fetch instruction, you can do an atomic addition by one and get back the new value (after the increment) as an atomic operation.

Pseudo code:

*StorageValue++;
NewValue = *StorageValue;
return newValue;

FPL has several versions for various datatypes:

Usage:

volatile uint32_t storageValue;
uint32_t newValue = fplAtomicAddAndFetchU32(&storageValue);
fpl_platform_api uint32_t fplAtomicAddAndFetchU32(volatile uint32_t *dest, const uint32_t addend)
Adds the addend to destination 32-bit unsigned integer atomically and returns the result after the ad...

Exchange

With an Exchange instruction, you can do an atomic exchange and get back the previous value (before the change) as an atomic operation.

Pseudo code:

oldValue = *storageValue;
*storageValue = exchangeValue;
return oldValue;

FPL has several versions for various datatypes:

Usage:

volatile uint32_t storageValue;
uint32_t newValue = fplAtomicStoreU32(&storageValue, 1337);
fpl_platform_api void fplAtomicStoreU32(volatile uint32_t *dest, const uint32_t value)
Overwrites the 32-bit unsigned value atomically.
Note
The exchange atomic is identical to Store, except that it returns the previous value before the change.

Store

With a Store instruction, you can do a memory write as an atomic operation.

Pseudo code:

*storageValue = newValue;

FPL has several versions for various datatypes:

Usage:

volatile uint32_t storageValue;
fplAtomicStoreU32(&storageValue, 1337);
Note
The store atomic is identical to Exchange, except that it does not return any value.

Load

With a Load instruction, you can do a memory read as an atomic operation.

Pseudo code:

return *storageValue;

FPL has several versions for various datatypes:

Usage:

volatile uint32_t storageValue;
uint32_t value = fplAtomicLoadU32(&storageValue);
fpl_platform_api uint32_t fplAtomicLoadU32(volatile uint32_t *source)
Loads the 32-bit unsigned value atomically and returns the value.

Compare-And-Swap

With a Compare-And-Swap instruction, you can check a value against another and exchange them as an atomic operation.
The previous/current value will always be returned regardless of the result.

Pseudo code:

oldValue = *storageValue;
if (oldValue == comparend) {
*storageValue = exchangeValue;
}
return oldValue;

FPL has several versions for various datatypes:

Usage:

volatile uint32_t storageValue = 0;
uint32_t oldValue = fplAtomicCompareAndSwapU32(&storageValue, 0, 1337);
// The first thread executing this will see 0 as the oldValue, all others will see 1337
// or
volatile uint32_t storageValue = 0;
if (fplAtomicIsCompareAndSwapU32(&storageValue, 0, 1337)) {
// The first thread executing this to 1337 will get here
} else {
// All other threads get here
}
fpl_platform_api bool fplAtomicIsCompareAndSwapU32(volatile uint32_t *dest, const uint32_t comparand, const uint32_t exchange)
Compares a 32-bit unsigned integer with a comparand and swaps it when comparand matches the destinati...
fpl_platform_api uint32_t fplAtomicCompareAndSwapU32(volatile uint32_t *dest, const uint32_t comparand, const uint32_t exchange)
Compares a 32-bit unsigned integer with a comparand and swaps it when comparand matches the destinati...

Memory Barriers

With memory barriers you can prevent the compiler from reordering memory reads/writes.
In FPL additional CPU fences may be added to ensure that the previous memory operations are completed before the future ones.

Without fences

int theValue;
int valueIsPublished = 0;
void updateValue(int newValue) {
// The compiler may reorder these two writes
theValue = newValue;
valueIsPublished = 1;
}
int getValue() {
// The compiler may move the read of value before the publish check
if (valueIsPublished) {
return theValue;
}
return -1;
}
Warning
This example may break even on a single-threaded environment!

With a read/write barrier

int theValue;
int valueIsPublished = 0;
void updateValue(int newValue) {
theValue = newValue;
// The value is written before the publish is set
valueIsPublished = 1;
}
int getValue() {
// The publish is read before the value is being returned
if (valueIsPublished) {
return theValue;
}
return -1;
}
fpl_platform_api void fplAtomicReadWriteFence(void)
Inserts a memory read and write fence/barrier.

Read or Write

In some special cases you don't have to issue a full memory barrier like fplAtomicReadWriteFence() - you can use a read or write barrier only:

  • Use fplAtomicReadFence() to ensure prior memory reads complete before future ones (and prevent the compiler from reordering memory reads).
  • Use fplAtomicWriteFence() to ensure prior memory writes complete before future ones (and prevent the compiler from reordering memory writes).

Atomics vs Barriers?

Atomic functions may include memory barriers to ensure that CPU memory read/write instructions are executed in order and to prevent the compiler from reordering memory read/writes.
This means you don't have to use any kind of memory barriers around atomic functions ;-)