Mutex vs Spinlock

Introduction
Concurrency is everywhere inside the Linux kernel. Multiple threads, processes, softirqs, tasklets, and interrupt handlers can access the same shared data simultaneously.
Without synchronization, this leads to:
Race conditions
Data corruption
Unpredictable behavior
Difficult-to-debug kernel crashes
Two of the most common synchronization primitives used by kernel developers are:
Mutex
Spinlock
Although both protect critical sections, they work very differently internally and are designed for different situations.
The Locking Problem
Imagine three kernel threads attempting to update the same shared counter:
shared_counter++;
If all threads execute this simultaneously, the result becomes unpredictable.
To avoid this, we protect the critical section:
lock();
shared_counter++;
unlock();
Only one thread can enter the protected region at a time.
The important question is:
What happens to the threads that fail to acquire the lock?
The answer depends on whether we're using a mutex or a spinlock.
How a Mutex Works
A mutex allows only one owner.
When a thread attempts to acquire a locked mutex:
It cannot proceed.
It is put to sleep.
The scheduler removes it from the CPU.
It waits until the mutex becomes available.
Example:
mutex_lock(&my_mutex);
/* critical section */
mutex_unlock(&my_mutex);
Characteristics
Blocking lock
Sleeping is allowed
Context switch occurs
Suitable for long critical sections
How a Spinlock Works
A spinlock behaves differently.
If a thread cannot acquire the lock:
It does not sleep
It repeatedly checks whether the lock is available
It continuously waits ("spins")
Example:
spin_lock(&my_lock);
/* critical section */
spin_unlock(&my_lock);
Conceptually:
while (lock_is_busy)
;
Real Linux implementations are much more optimized than this simple representation.
Why Spinning Can Be Faster
Putting a thread to sleep is expensive.
The kernel must:
Invoke the scheduler
Save thread state
Perform a context switch
Wake the thread later
Perform another context switch
A mutex therefore incurs at least:
Sleep Context Switch
+
Wakeup Context Switch
For very short critical sections, this overhead can exceed the actual work being protected.
The Performance Rule
Let:
t_locked= time spent in critical sectiont_ctxsw= context switch time
If:
t_locked < 2 × t_ctxsw
then using a mutex becomes inefficient.
The system spends more time managing threads than performing useful work.
This phenomenon is known as thrashing.
In such cases, a spinlock is often the better choice.
Mutex vs Spinlock Comparison
| Feature | Mutex | Spinlock |
|---|---|---|
| Waiting strategy | Sleep | Spin |
| Context switch | Yes | No |
| Can block | Yes | No |
| Can sleep inside critical section | Yes | No |
| Interrupt-safe | No | Yes |
| Overhead | Higher | Lower |
| Best for | Long critical sections | Short critical sections |
Atomic Context Changes Everything
The Linux kernel has a very important rule:
Code running in atomic context cannot sleep.
Examples include:
Interrupt handlers
Softirqs
Tasklets
Other atomic execution paths
Since mutexes sleep internally, they cannot be used here.
Incorrect:
irq_handler()
{
mutex_lock(&lock); /* WRONG */
}
Correct:
irq_handler()
{
spin_lock(&lock);
/* critical section */
spin_unlock(&lock);
}
Sleeping Inside a Spinlock Is Forbidden
This is a common beginner mistake.
Never do:
spin_lock(&lock);
msleep(100);
spin_unlock(&lock);
Or:
spin_lock(&lock);
wait_event(...);
spin_unlock(&lock);
A spinlock assumes the holder will release it quickly.
Sleeping while holding a spinlock can freeze the system or trigger kernel warnings.
Practical Decision Guide
Use a Spinlock When
Running in interrupt context
Running in atomic context
Critical section is extremely short
Sleeping is not allowed
Maximum performance is required
Example:
spin_lock(&lock);
counter++;
spin_unlock(&lock);
Use a Mutex When
Running in process context
Blocking I/O may occur
Sleeping may occur
Critical section is relatively long
Example:
mutex_lock(&lock);
copy_to_user(...);
mutex_unlock(&lock);
Determining the Current Context
Linux provides helpers to determine where code is executing.
Example:
if (in_task())
{
/* process context */
}
else
{
/* atomic or interrupt context */
}
General rule:
Process context → mutex or spinlock
Interrupt/atomic context → spinlock only
Mental Model
Think of the locks this way:
Mutex
"I can't get the lock. I'll sleep until someone wakes me."
Spinlock
"I can't get the lock. I'll keep checking until it becomes available."
That single difference determines almost every practical use case.
Key Takeaways
Both mutexes and spinlocks protect critical sections.
Mutex losers sleep and later wake up.
Spinlock losers remain active and wait.
Mutexes have higher overhead because of context switching.
Spinlocks are ideal for short, non-blocking operations.
Mutexes are ideal for longer operations that may sleep.
Never use a mutex in interrupt or atomic context.
Never sleep while holding a spinlock.
Choosing the correct lock is one of the most important design decisions in Linux kernel development. Understanding when threads sleep and when they spin is the foundation of writing safe kernel code.



