【问题标题】:Thread safe singleton in swiftswift中的线程安全单例
【发布时间】:2018-08-16 00:57:25
【问题描述】:

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

这里有一个非常简单的问题版本:

单例

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
    }
}

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

这个问题不是Using a dispatch_once singleton model in Swift 重复,因为(如果我理解正确的话)他们正在解决访问单例对象本身的问题,但不能确保阅读和其属性的写入是线程安全的。

【问题讨论】:

标签: swift thread-safety swift4


【解决方案1】:

感谢@rmaddy cmets 为我指明了正确的方向,我得以解决问题。

为了使Singleton线程的属性foo安全,需要进行如下修改:

    class Singleton {

    static let shared = Singleton()

    private init(){}

    private let internalQueue = DispatchQueue(label: "com.singletioninternal.queue",
                                              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 标志的作用是确保在队列中所有先前计划的工作项都完成后执行该工作项。

【讨论】:

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

Swift 线程安全单例

[GCD]

[Swift barrier flag for thread safe]

您可以使用 GCD 和 3 个主要内容实现 Swift 的 Singleton 模式以实现并发环境:

  1. 自定义并发队列 - 本地队列以获得更好的性能,可以同时发生多个读取
  2. sync - customQueue.sync 用于读取共享资源 - 拥有清晰的 API 而无需回调
  3. barrier flag - customQueue.async(flags: .barrier) 用于写入操作:等待运行操作完成 -> 执行写入任务 -> 继续执行任务
public class MySingleton {

    public static let shared = Singleton()
    
    //1. custom queue
    private let customQueue = DispatchQueue(label: "com.mysingleton.queue", qos: .default, attributes: .concurrent)
    //shared resource
    private var sharedResource: String = "Hello World"

    //computed property can be replaced getters/setters
    var computedProperty: String {
        get {
            //2. sync read
            return customQueue.sync {
                sharedResource
            }
        }
        
        set {
            //3. async write
            customQueue.async(flags: .barrier) {
                sharedResource = newValue
            }
        }
    }
    
    private init() {
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多