Mastering iOS Locks: Types, Usage, and Performance Comparison
This article explains the relationship between locks and multithreading on iOS, introduces common lock types such as NSRecursiveLock, NSConditionLock, OSSpinLock, os_unfair_lock, pthread_mutex, and @synchronized, demonstrates resource contention with code examples, discusses lock selection, priority inversion, and provides a performance ranking of various locks.
01 Introduction
Locks are tightly coupled with multithreading; in a multithreaded environment, if thread
Aaccesses code protected by a lock, thread
Bmust wait for thread
Ato release the lock before it can access the same code.
02 Types
Here are the main lock types:
NSRecursiveLock: a recursive lock that can be acquired repeatedly by the same thread without deadlock, tracking lock and unlock counts.
NSConditionLock: a condition lock that allows a thread to acquire the lock only when a user‑defined condition is satisfied; it has the longest overhead among the locks.
OSSpinLock: a spin lock that stays in user space, reducing context switches and offering the highest performance, but it consumes CPU while spinning; for longer waits,
pthread_mutexis preferable.
os_unfair_lock: replaces
OSSpinLockdue to its issues.
pthread_mutex_t: a C‑level mutex with high performance at the API level.
@synchronized: a simple block‑based lock.
dispatch_semaphore: a semaphore provided by GCD.
NSLock: a high‑level mutex wrapper.
pthread_spinlock_t: a pthread spin lock (unsupported on iOS).
03 Resource Contention
When multiple threads read/write the same resource, resource contention can occur, leading to incorrect results. Locks or dispatch queues are typically used to solve this.
Example using
sleepto simulate different thread logic:
<code>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];
}
});
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]);
}
</code>The log shows that each thread reads the old value, sleeps, then writes back, causing later threads to overwrite earlier calculations—a classic resource contention scenario.
<code>count: 14, thread: <NSThread: 0x600003670f00>{number = 5, name = (null)}
count: 14, thread: <NSThread: 0x600003668840>{number = 6, name = (null)} // resource contention
... (remaining log omitted for brevity)
</code>04 Lock Classification
From the perspective of lock type:
4.1 Spin Locks
Spin locks are blind‑waiting locks that continuously consume CPU while waiting.
Example:
<code>- (void)method1 {
lock(lock);
// ...
unlock(lock);
}
- (void)method2 {
lock(lock);
// ...
unlock(lock);
}
</code>Spin locks are efficient because they avoid thread wake‑up overhead, looping on
while. For
OSSpinLock, the implementation is a loop between
a32and
a43.
4.2 Mutex Locks
When a mutex lock is waiting, the thread sleeps. For example,
pthread_mutex_lockinvokes a system call to put the thread to sleep until
unlockis called, which incurs scheduling overhead.
4.3 Recursive Locks
Recursive locks allow the same thread to lock the same object multiple times without deadlocking; the lock is released only after the number of
unlockcalls matches the number of
lockcalls.
<code>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");
}
NSLog(@"loop finished");
</code>4.4 Selection
Choose between mutex and spin locks based on scenario. Spin locks are suitable for short‑wait, CPU‑bound tasks on multi‑core CPUs; mutex locks are better for longer, more expensive tasks.
4.5 Priority Inversion
When high‑priority thread
Awaits for a lock held by low‑priority thread
B, and a medium‑priority thread
Cpreempts
B, priority inversion occurs. The system may temporarily raise
B's priority to resolve the issue.
05 Which Locks to Use
5.1 OSSpinLock
OSSpinLockis a spin lock but is deprecated on iOS;
os_unfair_lockintroduced in iOS 10 replaces it. While
OSSpinLockoffers top performance, it is no longer recommended for safety.
5.2 os_unfair_lock
os_unfair_lockis a mutex introduced in iOS 10. Unlike spin locks, it puts waiting threads to sleep, avoiding CPU consumption.
<code>typedef struct os_unfair_lock_s os_unfair_lock;
typedef struct os_unfair_lock_s *os_unfair_lock_t;
</code> <code>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);
</code>5.3 pthread_mutex_t
pthread_mutex_tis a C‑level mutex that can be configured as a normal or recursive lock.
<code>- (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);
}
</code>5.4 NSLock
NSLockis a high‑level wrapper around
pthread_mutex, providing simple lock/unlock methods.
<code>@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
@interface NSLock : NSObject <NSLocking>
@property (nullable, copy) NSString *name;
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@end
</code>5.5 NSRecursiveLock
Allows the same thread to lock repeatedly; internally it uses
PTHREAD_MUTEX_RECURSIVE.
5.6 NSCondition
A condition lock built on
pthread_mutex_tand
pthread_cond, supporting
wait,
signal, and
broadcast.
<code>self.condition = [[NSCondition alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"thread:%@", [NSThread currentThread]);
[self lockMethod];
});
- (void)lockMethod {
[self.condition lock];
NSLog(@"lockMethod lock");
NSLog(@"lockMethod wait...");
[self.condition wait];
NSLog(@"lockMethod wait completion");
[self.condition unlock];
NSLog(@"lockMethod unlock");
}
</code>5.7 NSConditionLock
Extends
NSConditionby adding a condition value;
lockWhenCondition:and
unlockWithCondition:control execution order across threads.
<code>- (instancetype)initWithCondition:(NSInteger)condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
</code>5.8 DISPATCH_QUEUE_SERIAL
A serial GCD queue can act as a lock by ensuring tasks execute one after another.
<code>self.serialQueue = dispatch_queue_create("com.sohu.serialQueue", nil);
- (void)threadActionA {
NSLog(@"A begin");
dispatch_async(self.serialQueue, ^{ NSLog(@"A threadExecute"); });
}
</code>5.9 dispatch_semaphore_t
A GCD semaphore can limit concurrent access; setting its value to 1 creates a binary lock.
<code>self.semaphore = dispatch_semaphore_create(1);
for (NSInteger i = 0; i < 5; i++) {
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadAction) object:nil];
[thread start];
}
- (void)threadAction {
NSLog(@"semaphore waiting");
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"thread:%@", [NSThread currentThread]);
sleep(1);
dispatch_semaphore_signal(self.semaphore);
}
</code>5.10 @synchronized
Uses
objc_sync_enterand
objc_sync_exitunder the hood, effectively a recursive
pthread_mutex_tlock.
<code>@synchronized([self class]) {
[self threadAction];
}
</code>5.11 atomic
Objective‑C
atomicproperties generate getter/setter code that protects access with a spin lock (now implemented with
os_unfair_lock). This adds overhead, so frequent properties should avoid
atomicunless thread safety is required.
<code>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);
}
</code>06 Lock Performance Comparison
Performance ranking (from fastest to slowest) on iOS: OSSpinLock, dispatch_semaphore_t, pthread_mutex_t, DISPATCH_QUEUE_SERIAL, NSLock, NSCondition, os_unfair_lock, pthread_mutex_t (recursive), NSRecursiveLock, NSConditionLock, @synchronized. Choose locks based on 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
07 References
Priority Inversion Explained: https://zhuanlan.zhihu.com/p/146132061
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.
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.