# NSLock vs NSRecursiveLock vs NSCondition vs NSConditionLock in MulleObjC: When to Use Each

> Unlock MulleObjC concurrency: Understand NSLock, NSRecursiveLock, NSCondition, and NSConditionLock differences and when to use each for effective thread synchronization.

- Repository: [mulle-objc/mulleobjc](https://github.com/mulle-objc/mulleobjc)
- Tags: deep-dive
- Published: 2026-03-07

---

**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`](https://github.com/mulle-objc/mulleobjc/blob/main/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`](https://github.com/mulle-objc/mulleobjc/blob/main/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`](https://github.com/mulle-objc/mulleobjc/blob/main/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`](https://github.com/mulle-objc/mulleobjc/blob/main/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`](https://github.com/mulle-objc/mulleobjc/blob/main/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

```objc
#import "NSLock.h"

static NSLock *counterLock;
static NSInteger counter;

void incrementCounter(void)
{
    [counterLock lock];
    counter += 1;
    [counterLock unlock];
}

```

*Source reference:* [`src/class/NSLock.h`](https://github.com/mulle-objc/mulleobjc/blob/main/src/class/NSLock.h)

### Re-entrant Method Calls with NSRecursiveLock

```objc
#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`](https://github.com/mulle-objc/mulleobjc/blob/main/src/class/NSRecursiveLock.h)

### Producer-Consumer Queue with NSCondition

```objc
#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`](https://github.com/mulle-objc/mulleobjc/blob/main/src/class/NSCondition.h)

### State-Machine Coordination with NSConditionLock

```objc
#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`](https://github.com/mulle-objc/mulleobjc/blob/main/src/class/NSConditionLock.h), `src/class/NSConditionLock.m`

## Summary

- **NSLock** wraps `mulle_thread_mutex_t` for simple, non-reentrant critical sections in [`src/class/NSLock.h`](https://github.com/mulle-objc/mulleobjc/blob/main/src/class/NSLock.h)
- **NSRecursiveLock** adds owner tracking in `_thread` and depth counting in `_depth` for re-entrant method calls in [`src/class/NSRecursiveLock.h`](https://github.com/mulle-objc/mulleobjc/blob/main/src/class/NSRecursiveLock.h)
- **NSCondition** pairs a `pthread_mutex_t` with a `pthread_cond_t` for wait/signal patterns in [`src/class/NSCondition.h`](https://github.com/mulle-objc/mulleobjc/blob/main/src/class/NSCondition.h)
- **NSConditionLock** extends this with numeric `_currentCondition` for state-machine coordination in [`src/class/NSConditionLock.h`](https://github.com/mulle-objc/mulleobjc/blob/main/src/class/NSConditionLock.h) and `src/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`](https://github.com/mulle-objc/mulleobjc/blob/main/src/class/NSCondition.h) and [`src/class/NSLock.h`](https://github.com/mulle-objc/mulleobjc/blob/main/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`](https://github.com/mulle-objc/mulleobjc/blob/main/src/class/NSLock.h) recommend using `mulle_thread_mutex_t` directly, bypassing the Objective-C message dispatch overhead entirely.