Является ли lazy-свойство в Swift потокобезопасным?

Ответ

Нет, ленивая инициализация (lazy) в Swift по умолчанию не является потокобезопасной.

Проблема: Если несколько потоков одновременно обратятся к еще не инициализированному lazy-свойству, может произойти состояние гонки (race condition). Это может привести к тому, что инициализирующее замыкание выполнится более одного раза, а конечное значение будет неопределенным.

Пример небезопасного кода:

class DataManager {
    // Не потокобезопасное lazy-свойство
    lazy var config: [String: String] = {
        // Длительная операция...
        return ["key": "value"]
    }()
}
// Одновременный доступ из нескольких потоков -> риск.

Как обеспечить потокобезопасность?

  1. Инициализация до параллельного доступа: Гарантировать, что свойство будет инициализировано в главном потоке до его использования в других.
  2. Использование мьютекса или очереди: Вручную синхронизировать доступ.

    import Dispatch
    
    class DataManager {
        private let queue = DispatchQueue(label: "com.example.sync")
        private var _config: [String: String]?
    
        var config: [String: String] {
            queue.sync {
                if let value = _config { return value }
                let newValue = ["key": "value"] // Инициализация
                _config = newValue
                return newValue
            }
        }
    }
  3. Property Wrapper (Swift 5.1+): Создать или использовать готовый обертку (например, @Atomic), которая инкапсулирует логику синхронизации.

Вывод: При использовании lazy в многопоточной среде необходимо самостоятельно добавлять механизмы синхронизации.

Ответ 18+ 🔞

Да ты посмотри, какая история интересная вырисовывается! Сидит себе программист, пишет на Swift, такой весь в себе, умный. Объявляет lazy var, думает: «Ах, какая экономия! Инициализирую только когда надо». И в ус не дует.

А потом, блядь, приходит многопоточность. И начинается, ёпта, цирк.

В чём, сука, соль-то? Да в том, что стандартный lazy — он как Герасим из той истории, немой и глухой к округе. Если на него с разных потоков навалятся, пока он ещё не проинициализирован, он нихуя не сообразит, что его уже щас десять человек дергают. И может такую пургу устроить, что инициализирующее замыкание выполнится несколько раз, а в итоге хрен пойми что в свойстве окажется. Состояние гонки, блядь, классика!

Вот смотри, как это выглядит в коде, этот распиздяйский подход:

class DataManager {
    // Не потокобезопасное lazy-свойство
    lazy var config: [String: String] = {
        // Длительная операция...
        return ["key": "value"]
    }()
}
// Одновременный доступ из нескольких потоков -> риск.

Представь: десять потоков как крикнут «Эй, config!» — а он только проснулся. И десять раз полезет это замыкание выполнять. Ну, пиздец же.

И что делать-то, блядь?

  1. Самый простой способ — не выёбываться. Проинициализировать всё в одном, главном потоке, ДО того как запустишь эти свои потоки-шалуны. Тогда они придут — а там уже всё готово, конфетка лежит. Никаких гонок.
  2. Взять и прикрутить синхронизацию, как взрослый. Создать очередь, мьютекс — что душе угодно.

    import Dispatch
    
    class DataManager {
        private let queue = DispatchQueue(label: "com.example.sync")
        private var _config: [String: String]?
    
        var config: [String: String] {
            queue.sync {
                if let value = _config { return value }
                let newValue = ["key": "value"] // Инициализация
                _config = newValue
                return newValue
            }
        }
    }

    Вот теперь красота. Какой бы поток ни пришёл, он упрётся в queue.sync. Первый зашедший проинициализирует, остальные просто получат готовое значение. Порядок, блядь, а не бардак.

  3. Ну и для красоты — property wrapper. Начиная со Swift 5.1 можно завернуть всю эту логику с синхронизацией в красивую обёртку, типа @Atomic. Чтоб не повторяться, как попугай.

Короче, вывод, чувак: lazy — он не волшебный. Он просто ленивый. А в мире, где всё бегает параллельно, одной ленивости мало, нужна ещё и голова на плечах. Иначе будет как с Муму — потоки всё утопят в неразберихе, а ты потом будешь метаться и орать «Мууу!», но нихуя не исправишь.