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

Как добиться потокобезопасности коллекций?

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

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

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

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

Обеспечение потокобезопасности коллекций в iOS разработке

Достижение потокобезопасности коллекций — критически важный аспект разработки многопоточных приложений в iOS. Коллекции по умолчанию не являются потокобезопасными в Swift, что означает, что одновременное чтение и запись из разных потоков может привести к неопределенному поведению, крешам или повреждению данных.

Основные подходы к потокобезопасности

1. Использование очередей (DispatchQueue)

Наиболее распространённый подход — синхронизация доступа через serial DispatchQueue:

class ThreadSafeArray<T> {
    private var array: [T] = []
    private let queue = DispatchQueue(label: "com.example.threadSafeArray", attributes: .concurrent)
    
    func append(_ element: T) {
        queue.async(flags: .barrier) {
            self.array.append(element)
        }
    }
    
    var count: Int {
        var result = 0
        queue.sync {
            result = self.array.count
        }
        return result
    }
    
    subscript(index: Int) -> T? {
        get {
            var result: T?
            queue.sync {
                guard index < self.array.count else { return }
                result = self.array[index]
            }
            return result
        }
        set {
            guard let newValue = newValue else { return }
            queue.async(flags: .barrier) {
                self.array[index] = newValue
            }
        }
    }
}

Здесь .barrier флаг гарантирует, что запись происходит эксклюзивно, в то время как чтение может выполняться параллельно.

2. NSLock и другие примитивы синхронизации

Для более тонкого контроля можно использовать низкоуровневые примитивы:

class ThreadSafeDictionary<Key: Hashable, Value> {
    private var dictionary: [Key: Value] = [:]
    private let lock = NSLock()
    
    func setValue(_ value: Value, forKey key: Key) {
        lock.lock()
        defer { lock.unlock() }
        dictionary[key] = value
    }
    
    func value(forKey key: Key) -> Value? {
        lock.lock()
        defer { lock.unlock() }
        return dictionary[key]
    }
}

3. Actor в Swift 5.5+

С появлением Swift Concurrency акторы стали современным способом обеспечения потокобезопасности:

actor ThreadSafeCollection {
    private var items: [String] = []
    
    func add(_ item: String) {
        items.append(item)
    }
    
    func getAll() -> [String] {
        return items
    }
}

// Использование
Task {
    let collection = ThreadSafeCollection()
    await collection.add("New Item")
    let items = await collection.getAll()
}

Акторы гарантируют изоляцию состояния и сериализацию доступа к своим свойствам и методам.

Сравнение подходов

ПодходПреимуществаНедостатки
DispatchQueueГибкость, поддержка барьеровРиск deadlock, ручное управление
NSLockПроизводительность, контрольСложность отладки, риск забыть unlock
ActorВстроенная в язык поддержка, безопасностьТребует Swift 5.5+, async/await

Практические рекомендации

  1. Выбор стратегии:

    • Для iOS 13+ предпочитайте акторы
    • Для поддержки старых версий — DispatchQueue с барьерами
    • Для высокопроизводительных сценариев — NSLock или os_unfair_lock
  2. Избегайте распространённых ошибок:

    • Не используйте @synchronized (устарело в Swift)
    • Не полагайтесь на Array или Dictionary без синхронизации
    • Остерегайтесь гонок условий даже при использовании потокобезопасных коллекций
  3. Готовые решения:

    • NSCache — потокобезопасный кэш
    • Atomic свойства через property wrappers
    • Библиотеки вроде ConcurrentCollections для сложных сценариев

Пример комбинированного решения

@propertyWrapper
struct Atomic<Value> {
    private var value: Value
    private let lock = NSLock()
    
    init(wrappedValue: Value) {
        self.value = wrappedValue
    }
    
    var wrappedValue: Value {
        get {
            lock.lock()
            defer { lock.unlock() }
            return value
        }
        set {
            lock.lock()
            defer { lock.unlock() }
            value = newValue
        }
    }
}

class SharedData {
    @Atomic var counter: Int = 0
    @Atomic var flag: Bool = false
}

Ключевой принцип: потокобезопасность — это не только про коллекции, но и про проектирование всей системы доступа к общим данным. Всегда документируйте, какие коллекции потокобезопасны, а какие требуют внешней синхронизации, и используйте строгий аудит доступа к разделяемым ресурсам в code review.

Как добиться потокобезопасности коллекций? | PrepBro