Поток безопасный синглтон в 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 главные вещи:

  1. Настраиваемая параллельная очередь - локальная очередь для повышения производительности, при которой одновременно может выполняться несколько операций чтения
  2. sync для чтения общего ресурса - иметь понятный API без обратных вызовов
  3. barrier флаг для операции записи - дождаться завершения выполняемых операций -> выполнить задачу записи -> продолжить выполнение taks
Другие вопросы по тегам