Поток безопасный синглтон в Swift
У меня есть и приложение, которое имеет синглтон, который хранит информацию по всему приложению. Тем не менее, это создает некоторые проблемы гонки данных при использовании синглтона из разных потоков.
Здесь есть очень дурацкая и упрощенная версия проблемы:
одиночка
class Singleton {
static var shared = Singleton()
var foo: String = "foo"
}
Использование синглтона (из AppDelegate для простоты)
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
DispatchQueue.global().async {
var foo = Singleton.shared.foo // Causes data race
}
DispatchQueue.global().async {
Singleton.shared.foo = "bar" // Causes data race
}
return true
}
}
Есть ли способ гарантировать, что синглтон является потокобезопасным, поэтому его можно использовать из любого места приложения, не беспокоясь о том, в каком потоке вы находитесь?
Этот вопрос не является дубликатом использования одноэлементной модели dispatch_once в Swift, поскольку (если я правильно понял) там они решают проблему доступа к самому объекту-одиночке, но не гарантируют, что чтение и запись его свойств выполнены нить безопасно.
2 ответа
Благодаря комментариям @rmaddy, которые указали мне правильное направление, я смог решить проблему.
Для того, чтобы сделать собственность foo
из Singleton
потокобезопасен, его необходимо изменить следующим образом:
class Singleton {
static var shared = Singleton()
private let internalQueue = DispatchQueue(label: "SingletionInternalQueue", qos: .default, attributes: .concurrent)
private var _foo: String = "aaa"
var foo: String {
get {
return internalQueue.sync { _foo }
}
set (newState) {
internalQueue.async(flags: .barrier) { self._foo = newState }
}
}
func setup(string: String) {
foo = string
}
}
Безопасность потока достигается наличием вычисляемого свойства foo
который использует internalQueue для доступа к "реальному" _foo
имущество.
Кроме того, чтобы иметь лучшую производительность internalQueue
создается как одновременный. А это значит, что нужно добавить barrier
флаг при записи в собственность.
Что за barrier
флаг делает так, чтобы рабочий элемент был выполнен после завершения всех ранее запланированных рабочих элементов в очереди.
[синхронизация против асинхронной]
Вы можете реализовать шаблон Swift Singleton для параллельной среды, используя
GCD
и 3 главные вещи:
- Настраиваемая параллельная очередь - локальная очередь для повышения производительности, при которой одновременно может выполняться несколько операций чтения
-
sync
для чтения общего ресурса - иметь понятный API без обратных вызовов -
barrier
флаг для операции записи - дождаться завершения выполняемых операций -> выполнить задачу записи -> продолжить выполнение taks