我在Swift 3中创建了一个简单的单例:
class MySingleton {
private var myName: String
private init() {}
static let shared = MySingleton()
func setName(_ name: String) {
myName = name
}
func getName() -> String {
return myName
}
}
由于我将init()
私有,并且还声明了shared
实例为static let
,我认为初始化程序是线程安全的。但是myName
的getter和setter函数呢,它们是否安全?
答案 0 :(得分:15)
稍微不同的方法(这来自Xcode 9 Playground)是使用并发队列而不是串行队列。
final class MySingleton {
static let shared = MySingleton()
private let nameQueue = DispatchQueue(label: "name.accessor", qos: .default, attributes: .concurrent)
private var _name = "Initial name"
private init() {}
var name: String {
get {
var name = ""
nameQueue.sync {
name = _name
}
return name
}
set {
nameQueue.async(flags: .barrier) {
self._name = newValue
}
}
}
}
.barrier
异步调度设置新值。该块可以异步执行,因为调用者不需要等待设置该值。只有在其前面的并发队列中的所有其他块完成之后,才会运行该块。因此,当此setter等待运行时,现有的挂起读取不会受到影响。屏障意味着当它开始运行时,不会运行其他块。实际上,在设置器的持续时间内将队列转换为串行队列。在此块完成之前,不能再进一步读取。当块已完成时,已设置新值,此setter之后添加的任何getter现在可以同时运行。答案 1 :(得分:13)
你说的那些在你写的那些获取者不是线程安全的。在Swift中,目前实现此目的的最简单(读取最安全)方法是使用Grand Central Dispatch队列作为锁定机制。最简单(也是最容易理解)的方法是使用基本的串行队列。
class MySingleton {
static let shared = MySingleton()
// Serial dispatch queue
private let lockQueue = DispatchQueue(label: "MySingleton.lockQueue")
private var _name: String
var name: String {
get {
return lockQueue.sync {
return _name
}
}
set {
lockQueue.sync {
_name = newValue
}
}
}
private init() {
_name = "initial name"
}
}
使用串行调度队列将保证先进先出执行以及实现"锁定"关于数据。也就是说,在更改数据时无法读取数据。在这种方法中,我们使用sync
来执行实际的数据读取和写入,这意味着调用者总是被迫等待轮到其他类似于其他锁定原语。
注意:这不是most performant方法,但是阅读和理解起来很简单。这是一个很好的通用解决方案,可以避免竞争条件,但并不意味着为并行算法开发提供同步。
来源: https://mikeash.com/pyblog/friday-qa-2015-02-06-locks-thread-safety-and-swift.html What is the Swift equivalent to Objective-C's "@synchronized"?