Mobile Development 18 min read

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.

Sohu Smart Platform Tech Team
Sohu Smart Platform Tech Team
Sohu Smart Platform Tech Team
Mastering iOS Locks: From Spin Locks to Semaphores Explained

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.

Performance chart
Performance chart

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

iOSSynchronizationmultithreadingpthreadSemaphoreLocks
Sohu Smart Platform Tech Team
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.