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

Какие знаешь локи?

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

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

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

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

Синхронизация потоков в iOS

В iOS и многопоточном программировании вообще существует несколько основных механизмов (локов) для обеспечения потокобезопасности и координации доступа к общим ресурсам из разных потоков. Их правильный выбор критически важен для производительности и предотвращения дедлоков и гонок.

Я разделю их на несколько категорий для структурированного ответа.

1. Основные примитивы низкого уровня (POSIX / Mach)

Эти локи предоставляются базовой операционной системой.

POSIX мьютексы (pthread_mutex_t)

  • Основа: Чистый C API из библиотеки pthread.
  • Характеристики: Высокая производительность, возможность настройки атрибутов (например, рекурсивный).
  • Пример использования:
#include <pthread.h>

pthread_mutex_t mutex;

void initMutex() {
    pthread_mutex_init(&mutex, NULL);
}

void criticalSection() {
    pthread_mutex_lock(&mutex);
    // Работа с общим ресурсом
    pthread_mutex_unlock(&mutex);
}

Spinlock (OSSpinLock)

  • История: Ранее использовался (OSSpinLock), но в современных системах признан устаревшим и опасным. Он "вращается" (busy-wait), потребляя ЦП, пока не получит блокировку. На системах с качеством обслуживания (QoS) это может привести к инверсии приоритетов и "голоданию" высокоприоритетных потоков.
  • Современная альтернатива: os_unfair_lock.

Unfair Lock (os_unfair_lock)

  • Основа: Низкоуровневый, высокопроизводительный замок из <os/lock.h>.
  • Характеристики: Замена OSSpinLock. Не является "справедливым" (fair) – не гарантирует порядок захвата, что повышает производительность. Рекомендуется для очень коротких критических секций.
  • Пример:
import os.lock

var unfairLock = os_unfair_lock()

func accessResource() {
    os_unfair_lock_lock(&unfairLock)
    defer { os_unfair_lock_unlock(&unfairLock) }
    // Короткая работа с ресурсом
}

2. Механизмы уровня Foundation / GCD

Эти абстракции чаще всего используются в повседневной разработке под iOS.

NSLock и его варианты

  • NSLock: Объективно-ориентированная обертка вокруг POSIX мьютекса. Базовый замок.
  • NSRecursiveLock: Позволяет одному и тому же потоку захватывать его многократно без дедлока. Критически важен для рекурсивных функций.
  • NSCondition и NSConditionLock: Позволяют потокам ждать определенного... условия (condition). NSConditionLock блокируется на основе конкретного целочисленного значения.
  • Пример NSRecursiveLock:
let recursiveLock = NSRecursiveLock()

func recursiveFunction(_ value: Int) {
    recursiveLock.lock()
    defer { recursiveLock.unlock() }

    if value > 0 {
        // Можем снова вызвать lock() в этом же потоке без дедлока
        recursiveFunction(value - 1)
    }
}

GCD Очереди (DispatchQueue) как механизм синхронизации

  • Подход: Вместо явных замков часто используется serial очередь (последовательная). Все задачи, отправленные в такую очередь, выполняются строго по порядку, что автоматически защищает ресурс.
  • Методы: sync и async.
  • Пример:
let serialQueue = DispatchQueue(label: "com.example.serialQueue")

private var internalArray: [String] = []

var threadSafeArray: [String] {
    get {
        return serialQueue.sync {
            return internalArray
        }
    }
    set {
        serialQueue.async(flags: .barrier) {
            self.internalArray = newValue
        }
    }
}
  • Barrier для Concurrent очередей: Для concurrent очередей barrier задача гарантирует, что она выполнится в одиночку, пока все остальные задачи ждут. Это мощный паттерн для "один-пишет, много-

читает" (multiple-readers-single-writer).

3. Специализированные и высокоуровневые механизмы

Semaphore (DispatchSemaphore)

  • Концепция: Не совсем замок, а счетчик, управляющий доступом к пулу ресурсов. Позволяет ограничить количество потоков, одновременно выполняющих определенный код.
  • Использование: wait() уменьшает счетчик, signal() увеличивает. Инициализируется начальным значением.
  • Пример (ограничение сетевых запросов):
let semaphore = DispatchSemaphore(value:并与3) // Не более 3 параллельных задач

func performTask() {
    semaphore.wait()
    defer { semaphore.signal() }

    // Выполнение ограниченного по количеству экземпляров задания
}

Actor (Swift 5.5+)

  • Современная абстракция: Не "лок" в классическом смысле, а языковая модель для изоляции состояния. Компилятор гарантирует, что к изолированным данным актора в один момент времени имеет доступ только одна задача.
  • Принцип: Автоматическая синхронизация при использовании await. Устраняет множество ручных ошибок.
  • Пример:
actor BankAccount {
    private var balance: Double = 0

    func deposit(_ amount: Double) {
        balance += amount
    }

    func withdraw(_ amount: Double) -> Bool {
        guard balance >= amount else { return false }
        balance -= amount
        return true
    }

    func currentBalance() async -> Double {
        return balance // Доступ автоматически сериализуется
    }
}

Ключевые критерии выбора

  • Производительность: Для микро-оптимизаций – os_unfair_lock. В большинстве случаев достаточно serial DispatchQueue или NSLock.
  • Рекурсивность: Если возможен рекурсивный вызов – только NSRecursiveLock или аккуратный дизайн с очередями.
  • Ожидание условий: NSCondition или NSConditionLock.
  • Ограничение параллелизма: DispatchSemaphore.
  • Современный и безопасный дизайн: Actor (Swift Concurrency) или serial очередь с барьерами.
  • Читаемость и простота: Часто лучшим "локом" является правильная архитектура, минимизирующая общее состояние (shared state).

Понимание различий и сфер применения каждого из этих механизмов — обязательная компетенция для разработчика, пишущего многопоточный и отзывчивый код под iOS.

Какие знаешь локи? | PrepBro