Introduction
Locks are synchronization tools used to control access to shared state. They are needed because uncontrolled access to shared data can create race conditions, where correctness depends on timing.
The main idea is simple: a lock protects a critical section. Only a thread that successfully acquires the lock should execute the protected code region.
Lock Basics
A lock usually has two main operations: lock() and unlock().
The basic flow is: Thread asks for lock => Enters critical section => Updates shared state => Releases lock
lock(): The thread asks for permission to enter the protected region.
Free lock: If the lock is available, the thread acquires it and enters.
Busy lock: If another thread holds the lock, the thread must wait.
Critical section: The protected code that accesses shared mutable state.
unlock(): The thread releases the lock after finishing the protected work.
This gives mutual exclusion: only one thread can execute the protected critical section at a time.
Lock Basics
Choosing the Lock Region
A lock should protect the complete shared-state rule that must remain correct. This rule is often called an invariant.
If the locked region is too small, related operations may be split apart and the shared state can still become inconsistent. If the locked region is too large, correctness may be preserved, but concurrency drops because threads wait more than necessary.
A good lock region protects the full shared update while keeping unrelated work outside the critical section.
Mutex
A mutex is a mutual-exclusion lock. It allows only one thread to own the protected region at a time.
A mutex usually has ownership semantics. This means the thread that locks the mutex is responsible for unlocking it. Another thread should not unlock a mutex it does not own.
Mutexes are commonly used for:
Shared counters: Protecting increments and updates.
Shared queues: Protecting enqueue and dequeue operations.
Shared maps or caches: Preventing inconsistent reads and writes.
Shared file or socket state: Controlling access to process-level resources.
A mutex is not just a 0 or 1 variable. The ownership rule is an important part of its meaning.
Mutex
Spinlock
A spinlock is a lock where a waiting thread repeatedly checks whether the lock has become free. This repeated checking is called busy waiting.
Spinlocks are useful only when the expected wait is very short. They avoid sleep and wakeup overhead, but they keep using CPU while waiting.
A spinlock can be correct for mutual exclusion, but it can become inefficient under long waits or heavy contention.
Blocking Locks
A blocking lock puts a waiting thread to sleep instead of making it repeatedly check the lock.
If the lock is busy, the operating system can stop running the waiting thread and wake it later when the lock becomes available. This saves CPU during longer waits.
The tradeoff is overhead. Sleeping and waking a thread can cost more than spinning briefly, so blocking locks are better when the wait may be longer.
Spinlock & Blocking Locks
Spinning vs Blocking
Aspect | Spinning | Blocking |
|---|---|---|
Waiting behavior | Thread keeps checking | Thread sleeps |
CPU use while waiting | Uses CPU | Saves CPU |
Best for | Very short waits | Longer waits |
Main cost | Wasted CPU under contention | Sleep and wakeup overhead |
Many real mutex implementations combine both ideas. They may first try a fast user-space acquisition path, spin briefly if the wait looks short, and then block through the operating system if the lock remains busy.
Common Lock Mistakes
Locks solve race conditions only when they protect the right region in the right way.
Common mistakes include:
Forgetting to unlock: Other threads may wait forever.
Protecting only part of the invariant: The race can still exist.
Holding a lock during slow I/O: Many threads may block behind one slow operation.
Using one giant lock: Correctness may be simple, but concurrency becomes poor.
Using too many tiny locks: The design becomes hard to reason about.
Using inconsistent lock order: Threads may wait on each other and create deadlock.
The safest habit is to identify the shared state and the invariant before choosing the lock.
Summary
A lock controls entry into a critical section so shared-state updates happen safely. A mutex is a mutual-exclusion lock with ownership semantics, meaning the thread that acquires it is responsible for releasing it.
Spinlocks use busy waiting and fit very short waits. Blocking locks let waiting threads sleep and fit longer waits. Other types, such as read-write locks, recursive locks, adaptive locks, and ticket locks, exist because synchronization must balance correctness, fairness, CPU use, and concurrency.
Be the first to add a comment.