← Назад к вопросам

В чем разница между NSRecursiveLock и NSLock?

2.0 Middle🔥 131 комментариев
#Многопоточность и асинхронность

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Разница между NSLock и NSRecursiveLock в iOS/macOS разработке

Оба класса — NSLock и NSRecursiveLock — принадлежат к семейству thread synchronization primitives (примитивы синхронизации потоков) в Foundation и используются для обеспечения потокобезопасности. Однако их ключевое различие заключается в поведении при повторном захвате одним и тем же потоком.

Основное отличие: возможность рекурсивного захвата

NSLock — это нерекурсивный (non-recursive) мьютекс. Если поток, который уже владеет блокировкой, попытается захватить её снова, это приведёт к взаимной блокировке (deadlock) или неопределённому поведению.

let lock = NSLock()

func criticalSection() {
    lock.lock()
    // Если вызвать criticalSection() отсюда повторно — deadlock!
    lock.unlock()
}

NSRecursiveLock — это рекурсивный мьютекс. Он позволяет одному и тому же потоку захватывать блокировку многократно, без возникновения deadlock. Каждый вызов lock() должен быть сбалансирован соответствующим вызовом unlock().

let recursiveLock = NSRecursiveLock()

func recursiveCriticalSection(depth: Int) {
    recursiveLock.lock()
    if depth > 0 {
        recursiveCriticalSection(depth: depth - 1) // Безопасно!
    }
    recursiveLock.unlock()
}

Когда использовать каждый из них?

NSLock (обычная блокировка)

  • Идеален для простых критических секций, где нет рекурсивных вызовов.
  • Более легковесный и может быть чуть быстрее в сценариях без рекурсии.
  • Чётко обнаруживает логические ошибки: если разработчик по ошибке попытается повторно захватить блокировку, это сразу приведёт к deadlock, что поможет в отладке.
// Классический пример с NSLock
class ThreadSafeCounter {
    private var value = 0
    private let lock = NSLock()
    
    func increment() {
        lock.lock()
        defer { lock.unlock() } // Гарантирует разблокировку
        value += 1
    }
}

NSRecursiveLock (рекурсивная блокировка)

  • Необходим при работе с рекурсивными алгоритмами, которые требуют синхронизации.
  • Полезен в сложных объектах, где внутренние методы могут вызывать друг друга, и все они требуют защиты одного и того же ресурса.
  • Обязателен для реализации некоторых паттернов, например, когда публичный метод, уже взявший блокировку, вызывает приватный метод, которому также нужна эта блокировка.
// Пример, где необходим NSRecursiveLock
class RecursiveDataProcessor {
    private var data: [Int] = []
    private let lock = NSRecursiveLock()
    
    func processAll() {
        lock.lock()
        defer { lock.unlock() }
        processFrom(index: 0)
    }
    
    private func processFrom(index: Int) {
        lock.lock() // Безопасный повторный захват тем же потоком
        defer { lock.unlock() }
        
        guard index < data.count else { return }
        // ... некоторая обработка data[index] ...
        processFrom(index: index +, 1) // Рекурсивный вызов
    }
}

Технические детали и внутренняя реализация

Оба класса являются обёртками (wrappers) над низкоуровневыми примитивами POSIX. NSLock обычно оборачивает pthread_mutex_t с атрибутом PTHREAD_MUTEX_NORMAL, а NSRecursiveLockpthread_mutex_t с атрибутом PTHREAD_MUTEX_RECURSIVE.

Важные общие свойства:

  • Оба поддерживают метод try(), который пытается захватить блокировку без ожидания и возвращает Bool.
  • Оба могут использоваться с lock(before:) для попытки захвата до определённого Date.
  • Не являются value-типами — это ссылочные типы (классы), как и большинство объектов Foundation.

Предостережения и лучшие практики

  1. Всегда балансируйте lock() и unlock(). Для избежания ошибок используйте defer в Swift.
  2. Рекурсивные блокировки могут маскировать проблемы дизайна. Если вам кажется, что нужен NSRecursiveLock, пересмотрите архитектуру — возможно, стоит разделить ресурсы или использовать более простые блокировки.
  3. Рассмотрите альтернативы. В современном Swift часто предпочтительнее:
    * `DispatchQueue` с атрибутом `.serial`
    * Акторы (Actors) в Swift 5.5+
    * `os_unfair_lock` для максимальной производительности (но без рекурсии)

// Современная альтернатива с DispatchQueue
class ModernCounter {
    private var value = 0
    private let queue = DispatchQueue(label: "com.example.serial.queue")
    
    func increment() {
        queue.sync {
            value += 1
        }
    }
}

Итог

КритерийNSLockNSRecursiveLock
Рекурсивный захватЗапрещён (deadlock)Разрешён
ПроизводительностьЧуть вышеЧуть ниже из-за учёта счётчика рекурсии
ИспользованиеПростые критические секцииРекурсивные алгоритмы, сложные взаимные вызовы
ОтладкаЖёстко выявляет ошибкиМожет скрывать архитектурные проблемы

Выбор между ними — это вопрос семантики, а не производительности. Если ваш код по своей природе рекурсивно требует доступа к защищаемому ресурсу из одного потока — используйте NSRecursiveLock. Во всех остальных случаях стандартный NSLock является более предсказуемым и часто более правильным выбором.

В чем разница между NSRecursiveLock и NSLock? | PrepBro