Xcode 错误报告 Swift 访问竞争条件
Xcode Incorrectly Reporting Swift Access Race Condition
我认为 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为scheme开启时):
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
应该处理并发。
我可以解决这个问题,在测试中,将获取和设置包装在 DispatchQueue 中,类似于 SynchronizedDictionary
:
的实际实现
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 有问题,还是我遗漏了什么?
线程清理程序向
报告 Swift access race
var syncDict = SynchronizedDictionary<String, String>()
结构,因为在
处有一个变异访问(通过下标setter)
syncDict["\(i)"] = "\(i)"
来自一个线程,并且在
处对同一结构进行只读访问(通过下标getter)
_ = syncDict["\(i)"]
来自不同的线程,没有同步。
这与 private var dictionary
属性 的冲突访问无关,也与下标方法 内部 发生的事情完全无关。如果将结构简化为
,您将获得相同的“Swift 访问竞争”
public struct SynchronizedDictionary<K: Hashable, V> {
private let dummy = 1
public subscript(key: String) -> String {
get {
return key
}
set {
}
}
}
所以这是来自线程清理器的正确报告,不是错误。
一个可能的解决方案是定义一个 class 而不是:
public class SynchronizedDictionary<K: Hashable, V> { ... }
这是一个引用类型,下标 setter 不再改变 syncDict
变量(它现在是指向实际对象存储的“指针”)。通过该更改,您的代码可以正常运行。
我认为 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为scheme开启时):
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
应该处理并发。
我可以解决这个问题,在测试中,将获取和设置包装在 DispatchQueue 中,类似于 SynchronizedDictionary
:
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 有问题,还是我遗漏了什么?
线程清理程序向
报告 Swift access racevar syncDict = SynchronizedDictionary<String, String>()
结构,因为在
处有一个变异访问(通过下标setter)syncDict["\(i)"] = "\(i)"
来自一个线程,并且在
处对同一结构进行只读访问(通过下标getter)_ = syncDict["\(i)"]
来自不同的线程,没有同步。
这与 private var dictionary
属性 的冲突访问无关,也与下标方法 内部 发生的事情完全无关。如果将结构简化为
public struct SynchronizedDictionary<K: Hashable, V> {
private let dummy = 1
public subscript(key: String) -> String {
get {
return key
}
set {
}
}
}
所以这是来自线程清理器的正确报告,不是错误。
一个可能的解决方案是定义一个 class 而不是:
public class SynchronizedDictionary<K: Hashable, V> { ... }
这是一个引用类型,下标 setter 不再改变 syncDict
变量(它现在是指向实际对象存储的“指针”)。通过该更改,您的代码可以正常运行。