Mastering iOS Locks: From Spin Locks to Semaphores Explained
This article explains the purpose, actions, and common types of locks in iOS—including spin locks, mutexes, semaphores, and read‑write locks—provides practical code examples, discusses underlying principles such as busy‑waiting and priority inversion, and compares performance across different lock implementations.
What Is a Lock?
A lock prevents multiple threads from accessing the same resource simultaneously, avoiding resource contention and program crashes.
Lock Actions
Locks have two fundamental actions: acquiring (locking) and releasing (unlocking).
Common Lock Types
Spin Lock (Busy‑Wait)
Continuously attempts to acquire the lock until successful, wasting CPU cycles if the lock is held for a long time.
while (tryLock() == false) {
// keep trying
}Because the CPU is busy spinning, it can cause priority inversion when a low‑priority thread holds the lock and a high‑priority thread busy‑waits.
Mutex (Mutual‑Exclude Lock)
while (tryLock() == false) {
// thread sleeps until the lock state changes
}The operating system puts the waiting thread to sleep, causing a context switch and higher overhead than a spin lock.
Semaphore
Introduced by Dijkstra in 1965, a semaphore maintains a counter between 0 and a maximum value.
dispatch_semaphore_t lock = dispatch_semaphore_create(1);
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
// critical section
dispatch_semaphore_signal(lock);When the counter is 0, threads block until the semaphore is signaled.
How to Use Locks in iOS
OSSpinLock
OSSpinLock lock = OS_SPINLOCK_INIT;
for (int i = 0; i < count; i++) {
OSSpinLockLock(&lock);
// ...
OSSpinLockUnlock(&lock);
}Deprecated after iOS 10 due to priority inversion issues.
pthread_mutex
pthread_mutex_t lock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
pthread_mutex_init(&lock, &attr);
pthread_mutex_lock(&lock);
// critical section
pthread_mutex_unlock(&lock);
pthread_mutexattr_destroy(&attr);Supports recursive, error‑checking, and normal modes; a recursive mutex is created by setting PTHREAD_MUTEX_RECURSIVE.
NSCondition
Wraps a POSIX condition variable ( pthread_cond_t) and a mutex, enabling producer‑consumer patterns.
pthread_mutex_t mutex;
pthread_cond_t cond;
// consumer
pthread_mutex_lock(&mutex);
while (!data) {
pthread_cond_wait(&cond, &mutex);
}
// process data
pthread_mutex_unlock(&mutex);
// producer
pthread_mutex_lock(&mutex);
// produce data
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);NSLock
#define MLOCK \
- (void)lock {\
int err = pthread_mutex_lock(&_mutex);\
// error handling ...\
}Internally a pthread_mutex with PTHREAD_MUTEX_ERRORCHECK for better diagnostics.
NSRecursiveLock
NSRecursiveLock *lock = [NSRecursiveLock new];
[lock lock];
// critical section
[lock unlock];Uses a recursive pthread_mutex under the hood.
@synchronized
@synchronized(obj) {
// critical section
}Objective‑C syntax sugar that creates a recursive lock for the given object; it has the highest time complexity among the iOS locks.
Underlying Principles
Spin‑Lock Mechanics
Spin locks keep checking the lock state in a tight loop, which wastes CPU cycles if the lock is held for a long time. On multi‑core CPUs, true atomic operations require hardware support such as the LOCK prefix on x86.
while (!atomicCompareAndSwap(&lock, 0, 1)) {
// busy‑wait
}Priority Inversion
When a low‑priority thread holds a lock and a high‑priority thread busy‑waits, the high‑priority thread can block the low‑priority thread from releasing the lock, leading to missed CPU time slices.
Read‑Write Locks (pthread_rwlock_t)
Allow multiple concurrent readers but only one writer at a time.
static pthread_rwlock_t rwLock;
pthread_rwlock_init(&rwLock, NULL);
// reader
pthread_rwlock_rdlock(&rwLock);
// read data
pthread_rwlock_unlock(&rwLock);
// writer
pthread_rwlock_wrlock(&rwLock);
// modify data
pthread_rwlock_unlock(&rwLock);Writer starvation can occur if the default preference is for readers; setting the attribute PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP mitigates this.
iOS Read‑Write Lock Wrapper
Typical usage combines pthread_rwlock_rdlock and pthread_rwlock_wrlock inside dispatch_async blocks for concurrent reads and serialized writes.
dispatch_async(queue, ^{
pthread_rwlock_rdlock(&rwLock);
// read
pthread_rwlock_unlock(&rwLock);
});
dispatch_async(queue, ^{
pthread_rwlock_wrlock(&rwLock);
// write
pthread_rwlock_unlock(&rwLock);
});Performance Comparison
Benchmarks show that for lock/unlock counts up to 10 000, NSCondition and NSLock have similar latency (~0.9 ms). At 1 000 000 operations, NSCondition is slightly faster, and at 10 000 000 the gap widens but remains under 5 ms.
References
pthread_mutex_lock documentation
Apple Thread Safety guide
glibc source for pthread_mutex_lock
Various articles on pthread synchronization mechanisms
Stack Overflow discussion on condition variables vs semaphores
man7.org page for pthread_rwlockattr_setkind_np
Sohu Smart Platform Tech Team
The Sohu News app's technical sharing hub, offering deep tech analyses, the latest industry news, and fun developer anecdotes. Follow us to discover the team's daily joys.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
