Как обеспечить потокобезопасность (thread safety) для переменной в Swift?

Ответ

Потокобезопасность гарантирует корректность данных при одновременном доступе из нескольких потоков. Вот основные подходы в Swift:

1. Использование DispatchQueue с барьером (barrier) Наиболее распространённый и гибкий способ для чтения/записи.

class ThreadSafeContainer<T> {
    private var value: T
    private let queue = DispatchQueue(label: "com.example.threadsafe", attributes: .concurrent)

    init(value: T) { self.value = value }

    var currentValue: T {
        get {
            return queue.sync { value } // Конкурентное чтение
        }
        set {
            queue.async(flags: .barrier) { // Эксклюзивная запись
                self.value = newValue
            }
        }
    }
}

2. Использование мьютексов (Mutex) Более низкоуровневый, но эффективный контроль.

import os

class ThreadSafeValue<T> {
    private var value: T
    private let lock = os_unfair_lock()

    init(value: T) { self.value = value }

    func update(_ block: (inout T) -> Void) {
        os_unfair_lock_lock(&lock)
        defer { os_unfair_lock_unlock(&lock) }
        block(&value)
    }

    func read() -> T {
        os_unfair_lock_lock(&lock)
        defer { os_unfair_lock_unlock(&lock) }
        return value
    }
}

3. Property Wrapper (обёртка свойства) Удобный современный способ инкапсулировать логику синхронизации.

@propertyWrapper
struct ThreadSafe<T> {
    private var value: T
    private let lock = NSLock()

    init(wrappedValue: T) {
        self.value = wrappedValue
    }

    var wrappedValue: T {
        get {
            lock.lock()
            defer { lock.unlock() }
            return value
        }
        set {
            lock.lock()
            defer { lock.unlock() }
            value = newValue
        }
    }
}

// Использование
class Model {
    @ThreadSafe var counter: Int = 0
}

Важно: Потокобезопасность переменной не гарантирует потокобезопасность всей системы. Необходимо анализировать все точки доступа к общим данным.

Ответ 18+ 🔞

А, ну это же классика, блядь! Потокобезопасность, сука! Такая тема, от которой у новичков мозг вскипает, как каша в детсаду, а опытные только вздыхают и думают: "опять эту песню петь, нахуй".

Смотри, в чём суть-то, ёпта. Представь себе, у тебя есть одна бутылка водки на столе. И к ней одновременно тянутся руки из пяти разных потоков. Что будет? Правильно, драка, блядь, мордобой и все останутся без водки, а бутылка разобьётся. Вот чтобы такого пиздеца не было, и нужны эти все штуки — мьютексы, очереди и прочая хуйня.

1. Очередь с барьером — наш бро, проверенный временем Это как завести в свою квартиру строгого, но справедливого швейцара. Читать могут все, кто пришёл, а вот чтобы что-то записать или изменить — нужно ждать, пока все читатели уйдут, и только тогда тебе дадут зелёный свет. В коде это выглядит так, блядь:

class ThreadSafeContainer<T> {
    private var value: T
    private let queue = DispatchQueue(label: "com.example.threadsafe", attributes: .concurrent)

    init(value: T) { self.value = value }

    var currentValue: T {
        get {
            return queue.sync { value } // Читаем все вместе, как в библиотеке
        }
        set {
            queue.async(flags: .barrier) { // А пишем по одному, как в сортире
                self.value = newValue
            }
        }
    }
}

Главное, блядь, не перепутай — .sync для чтения, .async(.barrier) для записи. А то будет не потокобезопасность, а потокопиздец.

2. Мьютекс — старый, злой, но быстрый дед Это уже по-серьёзному, на низком уровне. Как будто ты не швейцара нанял, а поставил у бутылки здорового качка с битой, который говорит: "Подходи по одному, пидоры". Быстрее, но и ответственности больше, ебать.

import os

class ThreadSafeValue<T> {
    private var value: T
    private let lock = os_unfair_lock()

    init(value: T) { self.value = value }

    func update(_ block: (inout T) -> Void) {
        os_unfair_lock_lock(&lock)
        defer { os_unfair_lock_unlock(&lock) }
        block(&value)
    }

    func read() -> T {
        os_unfair_lock_lock(&lock)
        defer { os_unfair_lock_unlock(&lock) }
        return value
    }
}

defer — это наше всё, ёпта. Заблокировал — обязательно разблокируй, даже если посередине вылетит исключение. А то все потоки зависнут и будут ждать, пока солнце взойдёт на западе.

3. Property Wrapper — модный, молодёжный способ Swift, сука, не стоит на месте. Теперь можно сделать такую красивую обёрточку, что самому приятно смотреть. Выглядит как конфетка, а внутри всё тот же здоровый качок с битой.

@propertyWrapper
struct ThreadSafe<T> {
    private var value: T
    private let lock = NSLock()

    init(wrappedValue: T) {
        self.value = wrappedValue
    }

    var wrappedValue: T {
        get {
            lock.lock()
            defer { lock.unlock() }
            return value
        }
        set {
            lock.lock()
            defer { lock.unlock() }
            value = newValue
        }
    }
}

// Использование — просто пиздец как элегантно
class Model {
    @ThreadSafe var counter: Int = 0
}

Объявил свойство с @ThreadSafe и вроде как спать спокойно можешь. Но нет, блядь!

И вот тут, сука, главный подвох! Запомни раз и навсегда, как "Отче наш": потокобезопасность ОДНОЙ ПЕРЕМЕННОЙ — это НЕ потокобезопасность ВСЕЙ СИСТЕМЫ, блядь!

Это как поставить бронированную дверь в халупе, но оставить окно нараспашку. Можно сколько угодно синхронизировать доступ к counter, но если у тебя два потока сначала читают его значение, потом каждый что-то своё вычисляет, а потом пишут обратно — будет classic race condition, ёбаный в рот! Потому что между чтением и записью значение могло уже десять раз поменяться другим потоком.

Так что, чувак, не расслабляйся. Синхронизировал одну штуку — иди проверяй, нет ли ещё общих данных, к которым лезут со всех щелей. Анализируй ВСЕ точки доступа, а не одну. Иначе получится история, как с тем Герасимом: вроде всё правильно сделал, а в итоге — Муму, блядь, на дне.