swift中的线程安全单例

时间:2018-03-07 19:51:00

标签: swift thread-safety swift4

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

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

的Singleton

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的副本,因为(如果我理解正确的话),他们正在解决访问单身对象本身的问题,但不能确保读取和写入其属性是安全的线程。

2 个答案:

答案 0 :(得分:20)

感谢@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来访问" real" _foo财产。

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

barrier标志的作用是确保在队列中所有先前安排的工作项完成后执行工作项。

答案 1 :(得分:0)

[GCD]

[Swift barrier flag for thread safe]

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

  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() {
    }
}