Mobile Development 21 min read

Mastering iOS Locks: From NSLock to os_unfair_lock and Performance Tips

This article explains the relationship between locks and multithreading on iOS, introduces various lock types such as NSRecursiveLock, NSConditionLock, OSSpinLock, os_unfair_lock, pthread_mutex, and @synchronized, demonstrates resource contention with code examples, and compares their performance and usage scenarios.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Mastering iOS Locks: From NSLock to os_unfair_lock and Performance Tips

Introduction

Locks are tightly coupled with multithreading; when thread A accesses code protected by a lock, thread B must wait until A releases the lock before it can proceed.

Lock Types

Here are the main lock types used in iOS development:

NSRecursiveLock – a recursive lock that allows the same thread to acquire the lock multiple times without deadlocking.

NSConditionLock – a condition lock where the user defines a condition that must be satisfied before a thread can acquire the lock.

OSSpinLock – a spin lock that stays in user space, reducing context switches and offering the highest performance, but can waste CPU cycles under contention.

os_unfair_lock – replaces OSSpinLock to avoid priority‑inversion problems; it puts waiting threads to sleep instead of spinning.

pthread_mutex_t – a low‑level C mutex with high performance; can be used directly via the POSIX API.

@synchronized – a simple syntax‑sugar lock that internally uses objc_sync_enter and objc_sync_exit.

dispatch_semaphore_t – a GCD semaphore that can limit concurrent access to a resource.

DISPATCH_QUEUE_SERIAL – a serial GCD queue that serializes execution, effectively acting as a lock.

Resource Contention

When multiple threads read and write the same resource simultaneously, race conditions occur. The article shows a demo where three threads repeatedly call saleTicket while sleeping to simulate work, leading to outdated values overwriting newer ones.

self.count = 15;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

dispatch_async(queue, ^{
    for (int i = 0; i < 5; i++) {
        [self saleTicket];
    }
});

dispatch_async(queue, ^{
    for (int i = 0; i < 5; i++) {
        [self saleTicket];
    }
});

- (void)saleTicket {
    NSInteger oldCount = self.count;
    sleep(0.1);
    oldCount--;
    self.count = oldCount;
    NSLog(@"count: %ld, thread: %@", self.count, [NSThread currentThread]);
}

The log shows that each thread reads the old value, sleeps, then writes back, causing later threads to overwrite earlier results – a classic race condition.

Spin Lock vs. Mutex

A spin lock ( OSSpinLock) continuously polls the CPU while waiting, which is fast when the wait time is short but wastes CPU cycles under heavy contention. A mutex ( pthread_mutex_lock) puts the thread to sleep, reducing CPU usage but incurring a context‑switch overhead.

Choosing the Right Lock

For short‑duration critical sections on multi‑core CPUs, a spin lock is suitable. For longer operations, a mutex or os_unfair_lock is preferable to avoid excessive CPU consumption.

Priority Inversion

When a low‑priority thread holds a lock needed by a high‑priority thread, the high‑priority thread can be blocked, leading to priority inversion. The article includes a classic diagram illustrating this problem with threads A (low), B (medium), and C (high).

Priority inversion diagram
Priority inversion diagram

Specific Lock Implementations

OSSpinLock

Deprecated due to unsafe behavior; Apple recommends os_unfair_lock on iOS 10 and later.

os_unfair_lock

A modern unfair lock that puts waiting threads to sleep, avoiding CPU waste. It must be unlocked by the same thread that locked it.

extern void os_unfair_lock_lock(os_unfair_lock_t lock);
extern bool os_unfair_lock_trylock(os_unfair_lock_t lock);
extern void os_unfair_lock_unlock(os_unfair_lock_t lock);

pthread_mutex_t

Initialized with PTHREAD_MUTEX_DEFAULT for a normal mutex or PTHREAD_MUTEX_RECURSIVE for a recursive mutex. Remember to destroy it in dealloc.

- (void)dealloc {
    pthread_mutex_destroy(&_lock);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
    pthread_mutex_init(&_lock, &attr);
    pthread_mutexattr_destroy(&attr);
}

- (void)getUserInfo {
    pthread_mutex_lock(&_lock);
    // business logic
    pthread_mutex_unlock(&_lock);
}

NSLock

A high‑level wrapper around pthread_mutex. Re‑locking the same NSLock on the same thread causes a deadlock.

NSLock *lock = [[NSLock alloc] init];
[lock lock]; // work
[lock unlock];

NSRecursiveLock

Allows the same thread to lock multiple times; internally uses PTHREAD_MUTEX_RECURSIVE.

self.lock = [[NSRecursiveLock alloc] init];
for (NSInteger i = 0; i < 100; i++) {
    [self.lock lock];
    NSLog(@"locked");
}
for (NSInteger i = 0; i < 100; i++) {
    [self.lock unlock];
    NSLog(@"unlocked");
}

NSCondition

Combines a mutex with a condition variable, allowing threads to wait for a specific condition before proceeding.

- (void)lockMethod {
    [self.condition lock];
    NSLog(@"lockMethod lock");
    [self.condition wait];
    NSLog(@"lockMethod wait completion");
    [self.condition unlock];
}

- (void)unlockMethod {
    [self.condition lock];
    NSLog(@"unlockMethod lock");
    [self.condition broadcast];
    sleep(1);
    NSLog(@"unlockMethod unlock");
    [self.condition unlock];
}

NSConditionLock

Extends NSCondition with a numeric condition value, enabling ordered execution of multiple threads.

self.lock = [[NSConditionLock alloc] initWithCondition:0];
// Thread C
[self.lock lockWhenCondition:0];
// ...
[self.lock unlockWithCondition:1];
// Thread B
[self.lock lockWhenCondition:1];
// ...
[self.lock unlockWithCondition:2];
// Thread A
[self.lock lockWhenCondition:2];
// ...
[self.lock unlock];

DISPATCH_QUEUE_SERIAL

A serial GCD queue can serialize access to a resource, avoiding explicit lock management.

self.serialQueue = dispatch_queue_create("com.example.serialQueue", nil);
dispatch_async(self.serialQueue, ^{ /* critical section */ });

dispatch_semaphore_t

A semaphore with an initial value of 1 works as a binary lock, putting waiting threads to sleep.

self.semaphore = dispatch_semaphore_create(1);
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
// critical section
dispatch_semaphore_signal(self.semaphore);

@synchronized

Provides a simple syntax‑sugar lock that internally uses objc_sync_enter and objc_sync_exit, which are built on top of a recursive pthread_mutex.

@synchronized(self) {
    [self threadAction];
}

Atomic Property

In Objective‑C, the atomic attribute adds a spin lock (now implemented with os_unfair_lock) around the getter and setter to ensure thread‑safe access, but it does not protect direct ivar access.

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) {
    if (!atomic) {
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        *slot = newValue;
        slotlock.unlock();
    }
    objc_release(oldValue);
}

Performance Comparison

Ranking of iOS locks from fastest to slowest (roughly): OSSpinLock, dispatch_semaphore_t, pthread_mutex_t, DISPATCH_QUEUE_SERIAL, NSLock, NSCondition, os_unfair_lock, recursive pthread_mutex, NSRecursiveLock, NSConditionLock, @synchronized. Choose a lock based on the expected contention and execution frequency.

OSSpinLock

dispatch_semaphore_t

pthread_mutex_t

DISPATCH_QUEUE_SERIAL

NSLock

NSCondition

os_unfair_lock

pthread_mutex_t (recursive)

NSRecursiveLock

NSConditionLock

@synchronized

References

Priority Inversion Explained: https://zhuanlan.zhihu.com/p/146132061

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

iOSmultithreadingLocksObjective‑C
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

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.