silk.concurrency.atomic.
Atomic memory operations on raw pointers, with explicit memory orderings.
When to use#
Use these operations to build synchronization: counters, flags, lock words, and queues that
several threads read and write at the same time. For most code, the types in silk.concurrency.sync (Arc,
Mutex, Parker) are easier and safer.
Details#
Each operation is one indivisible access to a naturally aligned integer or data pointer. The
MemoryOrder argument is static, so the operation and its ordering are fixed at compile time.
The model is the C++20 model: an acquire load that reads a release store makes every write
before that store visible after the load.
Atomic.wait, Atomic.notifyOne and Atomic.notifyAll are a futex: a thread sleeps
until the value at an address changes. They work on Darwin on aarch64, Linux with GNU libc on
x86_64 or aarch64, and wasm32.
Gotchas#
A plain access that races with any other access to the same location is undefined behavior, also when the other access is atomic. Make every access to a shared location atomic, with the same width.
All operations except
Atomic.fenceare unsafe. The caller proves that the pointer is live and aligned.
Examples#
Count and claim a flag#
import silk.concurrency.atomic { Atomic, CompareExchange, MemoryOrder }
import silk.mem.pointer { Pointer }
// Claims a one-time flag. Only the first caller gets true.
fn claim(flag: *mut u32) -> bool {
return match unsafe Atomic.compareExchange<u32>(
flag,
0,
1,
MemoryOrder.Acquire,
MemoryOrder.Relaxed
) {
CompareExchange<u32>.Exchanged => true
CompareExchange<u32>.Observed { value } => false
}
}
pub fn main() -> i32 {
let mut hits: u64 = 0
let counter = Pointer.fromMutRef(&mut hits)
// fetchAdd returns the value before the add.
let before = unsafe Atomic.fetchAdd<u64>(counter, 5, MemoryOrder.Relaxed)
if before != 0 || unsafe Atomic.load<u64>(counter, MemoryOrder.Relaxed) != 5 {
return 1
}
let mut claimed: u32 = 0
let flag = Pointer.fromMutRef(&mut claimed)
if !claim(flag) || claim(flag) {
return 1
}
return 0
}Import as Atomic with import silk.concurrency.atomic { Atomic }.
Public declarations: 3.
MemoryOrder#
pub enum MemoryOrderThe ordering constraint of one atomic operation.
Details#
The model is the C++20 model: an acquire load that reads a release store makes every write before that store visible after the load. The order is a static argument, so the operation and its ordering are fixed at compile time.
Relaxed#
RelaxedAtomicity only. It orders no other access.
Acquire#
AcquireA load that later reads and writes cannot move before.
Release#
ReleaseA store that earlier reads and writes cannot move after.
AcquireRelease#
AcquireReleaseBoth acquire and release. Use it for read-modify-write operations and fences.
SequentiallyConsistent#
SequentiallyConsistentAcquire and release, plus one total order over every sequentially consistent operation.
CompareExchange#
pub union CompareExchange<T>The outcome of a compare-exchange.
Exchanged#
ExchangedThe pointee equaled the expected value and now holds the desired one.
Observed#
ObservedThe pointee held value, and nothing was stored.
Field value#
pub value: TThe value read instead of the expected one.
Atomic#
pub struct AtomicThe atomic operations over raw pointers, with explicit memory orderings.
When to use#
Use these operations to build synchronization: counters, flags, lock words, and queues that
several threads read and write at the same time. For most code, the types in silk.concurrency.sync (Arc,
Mutex, Parker) are easier and safer.
Details#
Each operation is one indivisible access to a naturally aligned integer or data pointer.
T is one of u8, u16, u32, u64, usize, i8, i16, i32, i64, isize, or a data
pointer for load, store, swap, and the compare-exchanges. The arithmetic and bitwise
operations take integers only and wrap on overflow.
Gotchas#
A plain access that races with any other access to the same location is undefined behavior, also when the other access is atomic. Make every access to a shared location atomic, with the same width.
All operations except
Atomic.fenceare unsafe. The caller proves that the pointer is live and aligned.
Implementation Atomic#
impl AtomicAssociated function load#
pub unsafe fn load<T: Copy>(pointer: *const T, static order: MemoryOrder) -> TReads the pointee atomically.
Safety#
The pointer must address live, initialized, naturally aligned storage for T. The order must
be Relaxed, Acquire, or SequentiallyConsistent.
Every access to the same location that can happen at the same time must be atomic and have
the same width.
Associated function store#
pub unsafe fn store<T: Copy>(pointer: *mut T, value: T, static order: MemoryOrder) -> ()Writes value to the pointee atomically.
Safety#
The pointer must address live, naturally aligned, writable storage for T. The order must be
Relaxed, Release, or SequentiallyConsistent.
Every access to the same location that can happen at the same time must be atomic and have
the same width.
Associated function swap#
pub unsafe fn swap<T: Copy>(pointer: *mut T, value: T, static order: MemoryOrder) -> TReplaces the pointee with value and returns the previous value.
Safety#
The pointer must address live, initialized, naturally aligned, writable storage for T.
Every access to the same location that can happen at the same time must be atomic and have
the same width.
Associated function compareExchange#
pub unsafe fn compareExchange<T: Copy>(pointer: *mut T, expected: T, desired: T, static success: MemoryOrder,
static failure: MemoryOrder) -> CompareExchange<T>Stores desired if the pointee equals expected. Otherwise it returns the value it read.
Details#
success orders the exchange. failure orders the read when nothing is stored. failure
must be Relaxed, Acquire, or SequentiallyConsistent.
Safety#
The pointer must address live, initialized, naturally aligned, writable storage for T.
Every access to the same location that can happen at the same time must be atomic and have
the same width.
Associated function compareExchangeWeak#
pub unsafe fn compareExchangeWeak<T: Copy>(pointer: *mut T, expected: T, desired: T, static success: MemoryOrder,
static failure: MemoryOrder) -> CompareExchange<T>Stores desired if the pointee equals expected. Otherwise it returns the value it read. It
can also fail spuriously: return Observed with a value equal to expected.
When to use#
Use it in a retry loop. Use Atomic.compareExchange when one attempt must decide.
Details#
This spurious failure lets a load-linked, store-conditional target use one attempt per call.
Call it in a loop that retries on any observed value. success orders the exchange. failure
orders the read when nothing is stored. failure must be Relaxed, Acquire, or
SequentiallyConsistent.
Safety#
The pointer must address live, initialized, naturally aligned, writable storage for T.
Every access to the same location that can happen at the same time must be atomic and have
the same width.
Associated function fetchAdd#
pub unsafe fn fetchAdd<T: Copy>(pointer: *mut T, value: T, static order: MemoryOrder) -> TAdds value to the pointee, wrapping on overflow, and returns the previous value.
Safety#
The pointer must address live, initialized, naturally aligned, writable storage for T.
Every access to the same location that can happen at the same time must be atomic and have
the same width.
Associated function fetchSubtract#
pub unsafe fn fetchSubtract<T: Copy>(pointer: *mut T, value: T, static order: MemoryOrder) -> TSubtracts value from the pointee, wrapping on overflow, and returns the previous value.
Safety#
The pointer must address live, initialized, naturally aligned, writable storage for T.
Every access to the same location that can happen at the same time must be atomic and have
the same width.
Associated function fetchBitAnd#
pub unsafe fn fetchBitAnd<T: Copy>(pointer: *mut T, value: T, static order: MemoryOrder) -> TStores the bitwise and of the pointee and value, and returns the previous value.
Safety#
The pointer must address live, initialized, naturally aligned, writable storage for T.
Every access to the same location that can happen at the same time must be atomic and have
the same width.
Associated function fetchBitOr#
pub unsafe fn fetchBitOr<T: Copy>(pointer: *mut T, value: T, static order: MemoryOrder) -> TStores the bitwise or of the pointee and value, and returns the previous value.
Safety#
The pointer must address live, initialized, naturally aligned, writable storage for T.
Every access to the same location that can happen at the same time must be atomic and have
the same width.
Associated function fetchBitXor#
pub unsafe fn fetchBitXor<T: Copy>(pointer: *mut T, value: T, static order: MemoryOrder) -> TStores the bitwise exclusive or of the pointee and value, and returns the previous value.
Safety#
The pointer must address live, initialized, naturally aligned, writable storage for T.
Every access to the same location that can happen at the same time must be atomic and have
the same width.
Associated function fetchMinimum#
pub unsafe fn fetchMinimum<T: Copy>(pointer: *mut T, value: T, static order: MemoryOrder) -> TStores the smaller of the pointee and value, and returns the previous value.
Safety#
The pointer must address live, initialized, naturally aligned, writable storage for T.
Every access to the same location that can happen at the same time must be atomic and have
the same width.
Associated function fetchMaximum#
pub unsafe fn fetchMaximum<T: Copy>(pointer: *mut T, value: T, static order: MemoryOrder) -> TStores the larger of the pointee and value, and returns the previous value.
Safety#
The pointer must address live, initialized, naturally aligned, writable storage for T.
Every access to the same location that can happen at the same time must be atomic and have
the same width.
Associated function wait#
pub unsafe fn wait(pointer: *const u32, expected: u32, timeout: Option<u64>) -> boolSleeps while the pointee equals expected, until a notify, the timeout, or a spurious wakeup.
Details#
The comparison and the sleep are one atomic step against Atomic.notifyOne and
Atomic.notifyAll. Thus a notify after a store that changed the pointee is never lost.
timeout is a relative bound in nanoseconds. Option.None waits with no bound. The result is
false only when the timeout ended the wait. A true result tells nothing about the pointee,
so check the condition again in a loop.
It works on Darwin on aarch64, Linux with GNU libc on x86_64 or aarch64, and wasm32.
Gotchas#
Single-threaded WebAssembly has no other thread to change the pointee. A wait that sleeps with
no timeout traps, and a wait with a timeout returns false at once.
Safety#
The pointer must address live, naturally aligned storage for a u32. Every other access to it
must be atomic.
Associated function notifyOne#
pub unsafe fn notifyOne(pointer: *const u32) -> ()Wakes at least one thread blocked in Atomic.wait on pointer, if any is.
Details#
Store the new value before the notify. A waiter that has not yet slept sees the changed value and does not sleep, so the wakeup is never lost. It works on Darwin on aarch64, Linux with GNU libc on x86_64 or aarch64, and wasm32.
Safety#
The pointer must address live, naturally aligned storage for a u32.
Associated function notifyAll#
pub unsafe fn notifyAll(pointer: *const u32) -> ()Wakes every thread blocked in Atomic.wait on pointer.
Details#
Store the new value before the notify. A waiter that has not yet slept sees the changed value and does not sleep, so the wakeup is never lost. It works on Darwin on aarch64, Linux with GNU libc on x86_64 or aarch64, and wasm32.
Safety#
The pointer must address live, naturally aligned storage for a u32.
Associated function fence#
pub fn fence(static order: MemoryOrder) -> ()Orders memory accesses around this point without accessing memory itself.
Details#
The order must not be Relaxed. A release fence before a relaxed store, paired with an
acquire fence after a relaxed load that reads it, synchronizes like a release store and an
acquire load.
Category: Concurrency.
Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2.