Final Platform Layer 0.9.8-beta
Loading...
Searching...
No Matches
Atomics

Overview

This section explains what atomics is and how they are used.
Since decades CPUs have hardware CPU instructions for doing thread-safe math and compare operations built-in.
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.

Fetch-And-Add (Add)

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

Pseudo code:

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

FPL have several versions for that 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 the new value back after the increment as an atomic operation.

Pseudo code:

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

FPL have several versions for that 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 a Exchange instruction, you can do an atomic exchange and get the previous value back before the change as an atomic operation.

Pseudo code:

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

FPL have several versions for that 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 atomics is identical to Store, except that the exchange atomics will return 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 have several versions for that for various datatypes:

Usage:

volatile uint32_t storageValue;
fplAtomicStoreU32(&storageValue, 1337);
Note
The store atomics is identical to Exchange, except that the store atomics won't return any value.

Load

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

Pseudo code:

return *storageValue;

FPL have several versions for that 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 have several versions for that 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 gets 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 move the write above the the value set
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()
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(), so you can just use a read or write barrier only:

  • You can use fplAtomicReadFence() to just ensure memory writes are completed before future ones only (Plus preventing from compiler reordering memory writes).
  • You can use fplAtomicWriteFence() to just ensure memory reads are completed before future ones only (Plus preventing from compiler reordering memory reads).

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 ;-)