我有和应用程序有一个单身,可以在整个应用程序中存储信息。但是,当使用来自不同线程的单例时,这会产生一些数据争用问题。
这里有一个非常虚拟和简单化的问题版本:
的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的副本,因为(如果我理解正确的话),他们正在解决访问单身对象本身的问题,但不能确保读取和写入其属性是安全的线程。
答案 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)
[Swift barrier flag for thread safe]
您可以使用 GCD
和 3 个主要内容为并发环境实现 Swift 的单例模式:
sync
- customQueue.sync
用于阅读共享资源 - 拥有清晰的 API,无需回调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() {
}
}