swift中的线程安全单例

nik*_*ano 13 thread-safety swift swift4

我有和应用程序有一个单一的存储整个应用程序的信息.但是,当使用来自不同线程的单例时,这会产生一些数据争用问题.

这里有一个非常虚拟和简单化的问题版本:

独生子

class Singleton {
    static var shared = Singleton()

    var foo: String = "foo"
}
Run Code Online (Sandbox Code Playgroud)

单例的使用(为简单起见,来自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
    }
}
Run Code Online (Sandbox Code Playgroud)

有没有办法确保单例是线程安全的,所以它可以在应用程序的任何地方使用,而不必担心你在哪个线程?

这个问题不是在Swift使用dispatch_once单例模型的重复,因为(如果我理解正确的话)那里他们正在解决访问单例对象本身的问题,但不能确保其属性的读写完成线程安全.

nik*_*ano 25

感谢@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
    }
}
Run Code Online (Sandbox Code Playgroud)

线程安全是通过使用计算属性来完成的,该属性foo使用internalQueue来访问"real" _foo属性.

此外,为了获得更好的性能internalQueue,创建为并发.这意味着barrier在写入属性时需要添加标志.

什么barrier标志的作用是确保当队列上的所有先前安排的工作项目已完成工作项目将被执行.

  • 如果同一个单例有成员 foo2,你是用同一个内部队列保护它,还是用不同的队列保护它? (2认同)
  • @nikano 类“Singleton”使用静态“shared: Singleton”实例,该实例在“Singleton”类的所有实例中都是相同的。但是“internalQueue”对象在“Singleton”的每个实例中都是不同的。iOS 如何知道提交到 **不同** `internalQueue` 对象的块应该被序列化以进行写入?Apple 文档暗示“label”仅用于调试。“internalQueue”不应该也是“static”吗? (2认同)
  • 为什么不使用串行队列而是使用并发队列? (2认同)
  • 因为串行队列不支持同时进行多个读取。由于我们不需要一次只阻止一次读取,因此我们使用并发队列来允许多次读取。然而,我们确实需要支持一次只写一次,因此我们使用屏障标志。 (2认同)