swift - Swift:线程安全的单例,为什么我们使用同步进行读取?
问题描述
在制作线程安全的 Singleton 时,建议使用同步进行读取,使用带屏障的异步进行写入操作。
我的问题是为什么我们使用同步读取?如果我们使用异步操作执行读取会发生什么?
以下是推荐的示例:
func getUser(id: String) throws -> User {
var user: User!
try concurrentQueue.sync {
user = try storage.getUser(id)
}
return user
}
func setUser(_ user: User, completion: (Result<()>) -> Void) {
try concurrentQueue.async(flags: .barrier) {
do {
try storage.setUser(user)
completion(.value(())
} catch {
completion(.error(error))
}
}
}
解决方案
使用并发队列的概念与“同时读取” sync
;write with barrier with async
”是一种非常常见的同步模式,称为“读写器”。这个想法是并发队列仅用于将写入与屏障同步,但读取将与其他读取同时发生。
所以,这里有一个简单的、真实的例子,使用读写器同步访问一些私有状态属性:
enum State {
case notStarted
case running
case complete
}
class ComplexProcessor {
private var readerWriterQueue = DispatchQueue(label: "...", attributes: .concurrent)
// private backing stored property
private var _state: State = .notStarted
// exposed computed property synchronizes access using reader-writer pattern
var state: State {
get { readerWriterQueue.sync { _state } }
set { readerWriterQueue.async { self._state = newValue } }
}
func start() {
state = .running
DispatchQueue.global().async {
// do something complicated here
self.state = .complete
}
}
}
考虑:
let processor = ComplexProcessor()
processor.start()
然后,稍后:
if processor.state == .complete {
...
}
计算state
属性使用读写器模式提供对底层存储属性的线程安全访问。它同步对某些内存位置的访问,我们相信它会响应。在这种情况下,我们不需要令人困惑@escaping
的闭包:sync
读取会生成非常简单且易于推理的代码。
话虽如此,在您的示例中,您不仅要与某些属性同步交互,还要与storage
. 如果那是保证响应的本地存储,那么读写器模式可能很好。
但是,如果storage
方法的运行时间可能超过几毫秒,那么您就不想使用读写器模式。可以抛出错误的事实getUser
让我想知道是否storage
已经在进行复杂的处理。即使它只是从某个本地存储快速读取,如果它后来被重构为与某个远程存储交互,会受到未知网络延迟/问题的影响怎么办?归根结底,假设值总是会很快返回,getUser
让方法对 的实现细节做出假设是有问题的。storage
在这种情况下,您将按照Jeffery Thomas 的建议重构getUser
方法以使用@escaping
完成处理程序闭包。我们永远不想有一个可能花费超过几毫秒的同步方法,因为我们永远不想阻塞调用线程(尤其是如果它是主线程)。
顺便说一句,如果你坚持读写模式,你可以简化你的getUser
, 因为sync
返回它的闭包返回的任何值:
func getUser(id: String) throws -> User {
return try concurrentQueue.sync {
try storage.getUser(id)
}
}
而且您不能try
与async
(仅在您的do
-catch
块内)一起使用。所以它只是:
func setUser(_ user: User, completion: (Result<()>) -> Void) {
concurrentQueue.async(flags: .barrier) {
do {
try storage.setUser(user)
completion(.value(())
} catch {
completion(.error(error))
}
}
}
推荐阅读
- python - 即使满足条件,显示消息语句也不会打印,我有点卡在这个上
- asp.net-core - 将 API 添加到现有 ASP.Net Core 2.2 MVC 解决方案的良好架构是什么
- python - 硒刮问题 | 拆分文本块以列出
- javascript - 如何设计和实现包含掩码的表单(类似于位掩码,但非二进制)?
- android - 在 Cmdline-tools 中找不到预制双簧管 REQUIRED CONFIG(版本 6858069)
- android-studio - 在 Kotlin 中构建失败并出现异常错误
- windows-10 - 如何使用免费工具集创建 MSI 安装程序以进行静默安装
- jenkins - 如何将 REST API 与指向共享库的 Jenkinsfile 一起使用?
- c++ - 改变
/ 的对比 - python - python scrapy从产品页面获取url列表