A spinlock is a synchronization primitive in which a thread or task repeatedly polls a lock variable in a tight loop ("spins") until the lock becomes available, rather than blocking and yielding the CPU. It is the simplest form of mutual exclusion and is most appropriate when the expected wait time is very short and the cost of a context switch would exceed the cost of spinning.
In practice
In bare-metal and RTOS-based embedded firmware, spinlocks appear most often in two contexts: protecting very short critical sections on single-core MCUs (where disabling interrupts is the more common alternative), and coordinating shared data between cores on multicore SoCs such as the Cortex-M33-based RP2040 or multicore Cortex-A/R devices. On a single-core MCU, a spinlock that can be contended by an ISR is a potential deadlock hazard — if the ISR preempts the thread holding the lock and then spins waiting for that same lock, the system hangs. For this reason, single-core bare-metal code typically uses interrupt-disable/enable pairs instead of spinlocks for ISR-safe mutual exclusion.
On multicore targets, spinlocks are a legitimate tool because a core can genuinely make forward progress while another core holds the lock. The RP2040, for example, provides 32 hardware spinlock registers (SIO_SPINLOCK0–SIO_SPINLOCK31) that can be atomically claimed and released by either core without needing a software atomic operation. Similarly, multicore Cortex-A SoCs running Linux or an SMP RTOS use spinlocks heavily in driver and kernel code.
A correct spinlock implementation requires either atomic read-modify-write instructions or explicit memory barriers to prevent the compiler and CPU from reordering the lock acquisition. On ARMv6-M (Cortex-M0/M0+), dedicated hardware exclusive-access instructions are not available; PRIMASK-based interrupt masking is the practical substitute, though some toolchains implement atomic-like behavior via library calls or critical sections. On ARMv7-M and ARMv8-M (Cortex-M3 and later), the LDREX/STREX exclusive-access instructions provide the atomic check-and-set needed for lock acquisition, but a correct spinlock also requires a retry loop and appropriate memory barriers around the critical section. C11 and C++11 stdatomic operations map to these instructions on supported toolchains.
The primary cost of a spinlock is wasted CPU cycles during contention. On power-sensitive embedded targets this can increase current draw noticeably. If the lock may be held for more than a handful of instruction cycles, a blocking mutex is usually the better choice — as discussed in "Mutex vs. Semaphore - Part 1", understanding the semantics of each primitive is essential to selecting the right one. Spinlocks are generally inappropriate in a cooperative RTOS scheduler, where a spinning task never yields and the lock holder can therefore never run; in such systems, checking the lock and yielding explicitly on retry is the correct approach.
Frequently asked
What is the difference between a spinlock and a mutex?
A
mutex blocks the calling thread when the lock is unavailable, causing the
scheduler to run another task. A spinlock keeps the calling thread running, burning CPU cycles until the lock is free. Mutexes are preferred when contention is possible and wait times are unpredictable. Spinlocks are preferred when wait times are known to be extremely short and the overhead of a
context switch would be disproportionate — most commonly in multicore drivers or very short critical sections.
Can I use a spinlock safely on a single-core MCU?
With caution. On a single-core
MCU, a spinlock can only make progress if the lock holder gets CPU time, which means the spinning task must eventually yield. In a
preemptive RTOS this can work if the lock holder has equal or higher priority and will run again, but it wastes
scheduler cycles. More critically, if an
ISR tries to acquire a spinlock held by a task it just preempted, the system will
deadlock. For most single-core bare-metal or RTOS designs, briefly disabling interrupts is the safer and more efficient alternative.
Why does a spinlock need memory barriers or atomic instructions?
Without barriers, the compiler or the CPU's out-of-order execution pipeline can reorder memory accesses, allowing code inside the
critical section to be observed before the lock is acquired, or after the lock is released. On ARM Cortex-M3 and later, the DMB (Data Memory Barrier) instruction prevents this reordering. LDREX/STREX provide the atomic check-and-set needed to avoid a race in the lock acquisition itself. Using plain load/store without these primitives produces a broken spinlock that may fail under optimization or on multicore hardware.
Does a spinlock make sense in a cooperative (non-preemptive) RTOS?
No. In a purely cooperative
scheduler, tasks yield voluntarily. If a task spins waiting for a lock, it never yields, so the task holding the lock never gets to run and release it. The result is a permanent hang. In cooperative systems, the correct approach is to check the lock and, if unavailable, explicitly yield and retry on the next scheduling round.
How does the RP2040 hardware spinlock differ from a software spinlock?
The RP2040 provides 32 hardware spinlock
registers in the SIO peripheral. Reading a free spinlock register atomically claims it and returns a nonzero value; reading an already-claimed one returns zero. Release is done by writing any value to the register. Because the claim-and-read is a single atomic bus operation performed in hardware, it is safe from races between the two Cortex-M0+ cores without needing LDREX/STREX, which are not available on ARMv6-M. Software spinlocks on ARMv6-M devices without such hardware support must rely on
interrupt masking or similar platform-specific mechanisms.
Differentiators vs similar concepts
Spinlock vs.
mutex: a mutex suspends the calling thread when blocked, freeing the CPU for other work; a spinlock keeps the thread running in a busy-wait loop. Spinlock vs.
interrupt disable: on single-core MCUs, briefly disabling interrupts (e.g., PRIMASK on Cortex-M) achieves mutual exclusion without the
deadlock risk a spinlock carries in ISR contexts. Spinlock vs.
semaphore: a semaphore is a signaling or counting primitive that also blocks the caller; it carries
scheduler overhead but supports patterns (like producer-consumer signaling) that a spinlock cannot express.