NSLock vs NSRecursiveLock vs NSCondition vs NSConditionLock in MulleObjC: When to Use Each
NSLock provides simple mutual exclusion for non-reentrant code, NSRecursiveLock supports nested locking by the same thread, NSCondition enables threads to wait for specific signals, and NSConditionLock combines locking with numeric condition variables for state-machine coordination in the MulleObjC runtime.
The MulleObjC runtime implements these Foundation synchronization primitives as thin wrappers around POSIX threading constructs in the mulle-objc/mulleobjc repository. Each class targets a distinct concurrency pattern, from basic critical sections to complex state-machine coordination across multiple threads.
Core Synchronization Primitives in MulleObjC
NSLock: Simple Mutual Exclusion
NSLock in src/class/NSLock.h wraps a mulle_thread_mutex_t to provide basic mutual exclusion. The class implements three core methods—lock, unlock, and tryLock—that map directly to the underlying mutex API.
According to the header comments in src/class/NSLock.h, developers should consider using the raw mulle_thread_mutex_t directly for very high contention scenarios where the Objective-C wrapper overhead becomes significant.
NSRecursiveLock: Re-entrant Protection
Defined in src/class/NSRecursiveLock.h, NSRecursiveLock inherits from NSLock and adds atomic _thread and _depth counters. When the same thread calls lock repeatedly, the implementation increments the depth counter instead of deadlocking.
This makes it ideal for recursive algorithms or when methods call back into synchronized code paths that would otherwise deadlock with a standard mutex.
NSCondition: Wait and Signal Coordination
NSCondition in src/class/NSCondition.h combines a pthread_mutex_t with a pthread_cond_t condition variable. The wait method releases the internal mutex and blocks until another thread invokes signal or broadcast, then automatically re-acquires the mutex upon waking.
This pattern supports producer-consumer queues and other scenarios where threads must pause until specific state changes occur, using the classic POSIX condition-variable pattern.
NSConditionLock: State-Machine Synchronization
NSConditionLock extends NSCondition with an atomic _currentCondition value, defined in src/class/NSConditionLock.h and implemented in src/class/NSConditionLock.m. The lockWhenCondition: method blocks until the stored condition matches the requested value, while unlockWithCondition: atomically updates the condition and releases the lock.
Specialized methods like mulleLockWhenNotCondition: and mulleUnlockWithCondition:broadcast: provide additional flexibility for complex state transitions where the condition value itself serves as the synchronization token.
Practical Implementation Examples
Protecting a Shared Counter with NSLock
#import "NSLock.h"
static NSLock *counterLock;
static NSInteger counter;
void incrementCounter(void)
{
[counterLock lock];
counter += 1;
[counterLock unlock];
}
Source reference: src/class/NSLock.h
Re-entrant Method Calls with NSRecursiveLock
#import "NSRecursiveLock.h"
@interface Reentrant : NSObject
{
NSRecursiveLock *_lock;
NSInteger _value;
}
- (void)increase;
@end
@implementation Reentrant
- (instancetype)init
{
self = [super init];
_lock = [NSRecursiveLock new];
return self;
}
- (void)increase
{
[_lock lock];
_value++;
if (_value < 5)
[self increase]; // safe: same thread can lock again
[_lock unlock];
}
@end
Source reference: src/class/NSRecursiveLock.h
Producer-Consumer Queue with NSCondition
#import "NSCondition.h"
@interface Queue : NSObject
{
NSCondition *_cond;
NSMutableArray *_items;
}
- (void)produce:(id)obj;
- (id)consume;
@end
@implementation Queue
- (instancetype)init
{
self = [super init];
_cond = [NSCondition new];
_items = [NSMutableArray new];
return self;
}
- (void)produce:(id)obj
{
[_cond lock];
[_items addObject:obj];
[_cond signal]; // wake one waiting consumer
[_cond unlock];
}
- (id)consume
{
[_cond lock];
while ([_items count] == 0)
[_cond wait]; // block until a producer signals
id obj = [_items objectAtIndex:0];
[_items removeObjectAtIndex:0];
[_cond unlock];
return obj;
}
@end
Source reference: src/class/NSCondition.h
State-Machine Coordination with NSConditionLock
#import "NSConditionLock.h"
typedef NS_ENUM(NSInteger, ServerState) {
ServerStateStopped = 0,
ServerStateRunning = 1,
ServerStatePaused = 2
};
@interface Server : NSObject
{
NSConditionLock *_stateLock;
}
- (void)start;
- (void)pause;
- (void)stop;
- (void)runLoop;
@end
@implementation Server
- (instancetype)init
{
self = [super init];
_stateLock = [[NSConditionLock alloc] initWithCondition:ServerStateStopped];
return self;
}
- (void)start
{
[_stateLock lockWhenCondition:ServerStateStopped];
[_stateLock unlockWithCondition:ServerStateRunning];
}
- (void)pause
{
[_stateLock lockWhenCondition:ServerStateRunning];
[_stateLock unlockWithCondition:ServerStatePaused];
}
- (void)stop
{
[_stateLock lock];
[_stateLock unlockWithCondition:ServerStateStopped];
}
- (void)runLoop
{
[_stateLock lockWhenCondition:ServerStateRunning];
// ... do work …
[_stateLock unlock];
}
@end
Source references: src/class/NSConditionLock.h, src/class/NSConditionLock.m
Summary
- NSLock wraps
mulle_thread_mutex_tfor simple, non-reentrant critical sections insrc/class/NSLock.h - NSRecursiveLock adds owner tracking in
_threadand depth counting in_depthfor re-entrant method calls insrc/class/NSRecursiveLock.h - NSCondition pairs a
pthread_mutex_twith apthread_cond_tfor wait/signal patterns insrc/class/NSCondition.h - NSConditionLock extends this with numeric
_currentConditionfor state-machine coordination insrc/class/NSConditionLock.handsrc/class/NSConditionLock.m
Frequently Asked Questions
When should I use NSRecursiveLock instead of NSLock in MulleObjC?
Use NSRecursiveLock when the same thread might need to acquire the lock multiple times without releasing it first, such as in recursive methods or when a callback chain re-enters the same synchronized code path. NSLock will deadlock if the owning thread attempts to lock again, while NSRecursiveLock tracks the owner and increments the _depth counter to allow nested acquisition.
How does NSCondition differ from NSConditionLock?
NSCondition provides basic wait and signal semantics using pthread_cond_t for simple producer-consumer scenarios. NSConditionLock adds a numeric _currentCondition value that threads can wait for specifically, making it preferable when coordinating state machines where threads must block until the system reaches a specific integer state, such as "initialized" or "running".
Can I use these locks across different threads in MulleObjC?
Yes, all four classes are designed for inter-thread synchronization. NSLock and NSRecursiveLock protect shared resources from concurrent access across any threads, while NSCondition and NSConditionLock enable cross-thread signaling and state coordination. The pthread primitives underlying src/class/NSCondition.h and src/class/NSLock.h are inherently thread-safe across any thread in the process.
What is the performance overhead of these Foundation locks?
MulleObjC implements these as minimal wrappers around POSIX primitives, with NSLock adding negligible overhead beyond mulle_thread_mutex_t operations. For maximum performance under extreme contention, the source comments in src/class/NSLock.h recommend using mulle_thread_mutex_t directly, bypassing the Objective-C message dispatch overhead entirely.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →