Here’s what “busy waiting” looks like in C++:
while (!has_work_to_do())
std::this_thread::yield();On Linux, the std::this_thread::yield() translates into a sched_yield system call, which means context switching to the kernel and back.
Instead, you can replace the standard_yield_t wrapper with a platform-specific micro-wait instruction, which is much cheaper.
Those instructions, like WFET on Arm, generally hint the CPU to transition to a low-power state.
| Wrapper | ISA | Instruction | Privileges |
|---|---|---|---|
x86_pause_t | x86 | PAUSE | R3 |
x86_tpause_t | x86+WAITPKG | UMONITOR+UMWAIT | R3 |
arm64_yield_t | AArch64 | YIELD | EL0 |
arm64_wfe_t | AArch64 | LDAXR+WFE | EL0 |
arm64_wfet_t | AArch64+WFXT | WFET | EL0 |
riscv64_pause_t | RISC-V | PAUSE | U |
riscv64_wrs_t | RISC-V+Zawrs | WRS.STO/WRS.NTO | U |
No kernel calls. No futexes. Works in tight loops.
The UMWAIT, WFE, WFET, and WRS wrappers go a step further than a spin hint: they sleep the core until a store to the watched line, capped by a timeout only where the loop guards more than one word, so the pool parks a worker in a low-power state rather than burning the core. spin_mutex<fu::preferred_yield_t> takes the same wrappers, while the default spin_mutex_t yields to the scheduler and Rust’s SpinMutex spins on core::hint::spin_loop - a futex-free std::mutex alternative, and the lock used in the NUMA example above.
On WebAssembly threads the waiter is wasm_wait_t: a 32- or 64-bit watch sleeps in memory.atomic.wait until a wasm_atomic_ref releasing write notifies or 1 µs passes, so pair the two; uncapped waits and 1- or 2-byte watches spin.