Ответ
Нет, стандартный Dictionary в Swift не является потокобезопасным. Одновременные операции чтения и, особенно, записи из разных потоков приводят к состоянию гонки (data race), что может вызвать неожиданные краши, повреждение данных или неконсистентное состояние коллекции.
Почему это происходит? Внутренняя структура словаря (хеш-таблица) не защищена от конкурентного доступа. Запись может изменять эту структуру (рехеширование), в то время как другой поток пытается читать.
Способы сделать Dictionary потокобезопасным:
-
Использование
DispatchQueueс барьером (barrier) для конкурентной очереди: Это эффективный способ, позволяющий множественное чтение параллельно, но блокирующий очередь на время записи.class ThreadSafeDictionary<Key: Hashable, Value> { private var dictionary = [Key: Value]() private let queue = DispatchQueue(label: "com.example.dictionaryQueue", attributes: .concurrent) func set(_ value: Value, for key: Key) { queue.async(flags: .barrier) { // Барьерная задача self.dictionary[key] = value } } func get(for key: Key) -> Value? { queue.sync { // Обычный синхронный вызов для чтения return self.dictionary[key] } } } -
Использование акторов (Actors) в Swift 5.5+: Акторы изолируют свое состояние, гарантируя, что к нему обращается только одна задача в данный момент.
actor SafeDictionaryActor<Key: Hashable, Value> { private var storage = [Key: Value]() func set(_ value: Value, for key: Key) { storage[key] = value } func get(for key: Key) -> Value? { return storage[key] } } // Использование: let dictActor = SafeDictionaryActor<String, Int>() Task { await dictActor.set(42, for: "answer") let value = await dictActor.get(for: "answer") } -
Использование примитивов синхронизации (
NSLock,pthread_rwlock_t): Более низкоуровневый, но иногда необходимый подход.
Альтернативы:
NSCache: Класс из Foundation, который частично потокобезопасен (не требует дополнительных блокировок для доступа), но автоматически удаляет объекты при нехватке памяти и не гарантирует сохранение всех элементов.- Специализированные библиотеки: Например,
ConcurrentDictionaryиз сторонних пакетов.
Вывод: Всегда обеспечивайте синхронизацию при доступе к изменяемому Dictionary из нескольких потоков.