Ios 单例中getter和setter的线程安全性
我在Swift 3中创建了一个简单的单例:Ios 单例中getter和setter的线程安全性,ios,swift,swift3,Ios,Swift,Swift3,我在Swift 3中创建了一个简单的单例: class MySingleton { private var myName: String private init() {} static let shared = MySingleton() func setName(_ name: String) { myName = name } func getName() -> String { return myN
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函数,它们是线程安全的吗?您所编写的getter不是线程安全的,这是正确的。在Swift中,目前实现这一点最简单(最安全)的方法是使用大型中央调度队列作为锁定机制。实现这一点的最简单(也是最容易推理的)方法是使用基本串行队列
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"
}
}
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
}
}
}
}
使用串行调度队列将保证先进先出的执行,并实现对数据的“锁定”。也就是说,数据在更改时无法读取。在这种方法中,我们使用sync
来执行数据的实际读取和写入,这意味着调用方将总是被迫等待轮到它,类似于其他锁定原语
注意:这不是方法,但它很容易阅读和理解。这是一个很好的通用解决方案,可以避免竞争条件,但并不意味着为并行算法开发提供同步
资料来源:
一种稍有不同的方法(来自Xcode 9游乐场)是使用并发队列而不是串行队列
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"
}
}
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
}
}
}
}
- 使用并发队列意味着来自多个线程的多个读取不会相互阻塞。由于获取时没有变异,因此可以同时读取该值,因为
- 我们正在使用
异步调度设置新值。该块可以异步执行,因为调用者不需要等待设置值。在前面的并发队列中的所有其他块完成之前,该块将不会运行。因此,当此setter等待运行时,现有的挂起读取不会受到影响。屏障意味着当它开始运行时,不会运行其他块。有效地,在setter期间将队列转换为串行队列。在该块完成之前,无法进行进一步读取。当块完成并设置了新值时,在此setter之后添加的任何getter现在都可以并发运行.barrier
name+=“bar”之类的东西
不是线程安全的,因为它包含一个读操作和一个写操作,另一个线程可以在这两个操作之间修改名称,就像这篇漂亮的文章中所描述的:非常有用的文章,也是最近的,非常感谢@d4Rk!我真的很喜欢这种变化。据我所知,它在数据一致性方面同样“正确”。唯一的细微差别是对setter的调用语义对于新手来说有点违反直觉,因为新手可能在“获取锁”时期待延迟。基本上,在执行setter之后,“有效设置”和“事实设置”,但这并不重要。@AllenHumphreys,使用“您的”(序列)或“Abizern的”(屏障)的方法取决于很多因素。在并发队列上使用屏障并不是违反直觉的,而是不同的。它有一些优点,也有一些缺点。有时它们都会失败。这与在串行队列上运行set
async和get
sync类似吗?@CyberMew这不是一回事。使用并发队列的意义在于,如果有多个读取,则不会相互阻塞。我概述的小偷方法意味着队列在读取时是并发的,但在写入时它表现为串行队列。是的。如果您开始写入,则随后添加的所有读卡器都将被阻止,直到写入完成