Essential building blocks for thread-per-core libraries.
- The
stdCargo feature (enabled by default) enables theThreadBuilderruntime integration API. derive(default) re-exports the#[derive(ThreadAware)]macro.- Disable default features for
#![no_std]environments. The core thread vocabulary, closures, and wrappers remain available. Enablederiveexplicitly if the derive macro is needed.
no_std environments require alloc and pointer-width atomics.
This crate re-exports every type from thread_aware_core, which is the authoritative stable
relocation contract. It adds derive support, closures, wrappers, runtime thread construction,
and runtime thread construction.
At a high level, this crate enables thread migrations of state via the ThreadAware trait:
- Runtimes (and similar) can use it to inform types that they were just moved across a thread or NUMA boundary.
- The authors of said types can then act on this information to implement performance optimizations. Such optimizations might include re-allocating memory in a new NUMA region, connecting to a thread-local I/O scheduler, or detaching from shared, possibly contended memory with the previous thread.
Similar to Clone, there are no exact semantic prescriptions of how types should behave on relocation.
They might continue to share some state (e.g., a common cache) or fully detach from it for performance reasons.
The primary goal is performance, so types should aim to minimize contention on synchronization primitives
and cross-NUMA memory access. Like Clone, the relocation itself should be mostly transparent and predictable
to users.
Relocation is always a callback on the current thread: the runtime must move or schedule the value
to the destination first, then invoke ThreadAware::relocate on that destination thread.
The destination argument describes the thread executing the callback; it does not identify a
remote thread for the callback to run on. This is important for implementations that release,
allocate, or initialize thread- and NUMA-local state.
Implementing ThreadAware
In most cases ThreadAware should be implemented via the provided derive macro.
As thread-awareness of a type usually involves letting all contained fields know of an ongoing
relocation, the derive macro does just that. A default impl is provided for many std types,
so the macro should ‘just work’ on most compounds of built-ins.
Relation to Send
ThreadAware requires Send as a supertrait. Types are first sent to the destination
thread, then the ThreadAware relocation notification is invoked on that same thread.
As this library is primarily intended for use in thread-per-core runtimes, we use the terms ‘thread’ and ‘core’ interchangeably. The assumption is that items primarily arrive on different threads, where each thread is pinned to a different CPU core. Should a runtime utilize more than one thread per core (e.g., for internal I/O) user code should be able to observe this fact.
ThreadAware vs. Unaware
Sometimes you might need to move inert types as-is, essentially bypassing all thread-aware handling. These might be foreign types that carry no allocation, do no I/O, or otherwise do not require any thread-specific handling.
Unaware can be used to encapsulate such types, a wrapper that itself implements ThreadAware, but
otherwise does not react to it. You can think of it as a MoveAsIs<T>. However, it was
deliberately named Unaware to signal that only types which are genuinely unaware of their
thread relocations (i.e., don’t impl ThreadAware) should be wrapped in such.
Wrapping types that implement the trait is discouraged, as it will prevent them from properly relocating and might have an impact on their performance, but not correctness, see below.
It is important to note that ThreadAware is a cooperative performance optimization and contention avoidance
primitive, not a guarantee of behavior for either the caller or callee. In other words, callers and runtimes must
continue to operate correctly if the trait is invoked incorrectly.
In particular, ThreadAware may not always be invoked when a type leaves the current thread.
While runtimes should reduce the incidence of that through their API design, it may nonetheless
happen via std::thread::spawn and other means. In these cases types should still function
correctly, although they might experience degraded performance through contention of now-shared
resources.
thread_aware_core implements ThreadAware for core, alloc, and standard library types.
Implementations for third-party types live with those types once the stable trait can be adopted
natively. Until then, inert foreign values can be wrapped in Unaware.
- The
stdCargo feature (enabled by default) enables theThreadBuilderruntime integration API. Disable it for#![no_std]environments; the crate then requiresallocand pointer-width atomics. derive(default): Re-exports the#[derive(ThreadAware)]macro from the companionthread_aware_macroscrate. Disable to avoid pulling in proc-macro code in minimal environments. For derive support withoutstd, usedefault-features = false, features = ["derive"].
Using the ThreadAware derive macro
When the derive feature (enabled by default) is active you can simply
use the ThreadAware derive macro instead of writing the
implementation manually.
use thread_aware::ThreadAware;
#[derive(Debug, Clone, ThreadAware)]
struct Point {
x: i32,
y: i32,
}This crate was developed as part of The Oxidizer Project. Browse this crate's source code.
