【发布时间】:2019-03-05 08:45:17
【问题描述】:
我认为 XCode 在我的 SynchronizedDictionary 中错误地报告了 Swift Access Race - 是吗?
我的SynchronizedDictionary 看起来像这样:
public struct SynchronizedDictionary<K: Hashable, V> {
private var dictionary = [K: V]()
private let queue = DispatchQueue(
label: "SynchronizedDictionary",
qos: DispatchQoS.userInitiated,
attributes: [DispatchQueue.Attributes.concurrent]
)
public subscript(key: K) -> V? {
get {
return queue.sync {
return self.dictionary[key]
}
}
mutating set {
queue.sync(flags: .barrier) {
self.dictionary[key] = newValue
}
}
}
}
以下测试代码将触发“Swift Access Race”问题(当为方案打开 Thread Sanitizer 时):
var syncDict = SynchronizedDictionary<String, String>()
let setExpectation = XCTestExpectation(description: "set_expectation")
let getExpectation = XCTestExpectation(description: "get_expectation")
let queue = DispatchQueue(label: "SyncDictTest", qos: .background, attributes: [.concurrent])
queue.async {
for i in 0...100 {
syncDict["\(i)"] = "\(i)"
}
setExpectation.fulfill()
}
queue.async {
for i in 0...100 {
_ = syncDict["\(i)"]
}
getExpectation.fulfill()
}
self.wait(for: [setExpectation, getExpectation], timeout: 30)
Swift Race Access 如下所示:
我真的没想到这里会出现访问竞争条件,因为SynchronizedDictionary 应该处理并发。
我可以通过在测试中将获取和设置包装在类似于SynchronizedDictionary 的实际实现的 DispatchQueue 中来解决此问题:
let accessQueue = DispatchQueue(
label: "AccessQueue",
qos: DispatchQoS.userInitiated,
attributes: [DispatchQueue.Attributes.concurrent]
)
var syncDict = SynchronizedDictionary<String, String>()
let setExpectation = XCTestExpectation(description: "set_expectation")
let getExpectation = XCTestExpectation(description: "get_expectation")
let queue = DispatchQueue(label: "SyncDictTest", qos: .background, attributes: [.concurrent])
queue.async {
for i in 0...100 {
accessQueue.sync(flags: .barrier) {
syncDict["\(i)"] = "\(i)"
}
}
setExpectation.fulfill()
}
queue.async {
for i in 0...100 {
accessQueue.sync {
_ = syncDict["\(i)"]
}
}
getExpectation.fulfill()
}
self.wait(for: [setExpectation, getExpectation], timeout: 30)
...但这已经在SynchronizedDictionary 内部发生了——那么为什么 Xcode 会报告访问竞争条件? - 是 Xcode 出了问题,还是我遗漏了什么?
【问题讨论】:
-
您的 2 个异步块可以分配给由您的并发队列控制的不同线程。这些线程中的任何一个都可以先执行。我怀疑它因此抱怨代码不能保证 setter 将在 getter 之前运行。您可以通过将其设为串行队列并查看错误是否消失来测试我的理论。
-
感谢您的反馈。我同意你的看法,用串行替换并发队列将解决问题。但是,这违背了 SynchronizedDictionary 的目的 - 能够同时访问它。
-
@PhillipMills 最后,setter 和 getter 都进入并发队列,而 setter 阻塞了一切。当一切都被阻止并等待设置器完成时,我看不出这是如何导致竞争条件的。
-
@PelleStenildColtau:请注意,在设置器中您可以异步(使用屏障)调度,例如medium.com/@oyalhi/dispatch-barriers-in-swift-3-6c4a295215d6。
标签: swift xcode multithreading race-condition ios-multithreading