swift - Xcode 错误地报告 Swift Access 竞争条件

标签 swift xcode multithreading race-condition ios-multithreading

我认为 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 看起来像这样:

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 出了问题,还是我遗漏了什么?

最佳答案

thread sanitizer 报告一个 Swift access race

var 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 {
        }
    }
}

所以这是来自线程清理程序的正确报告,而不是错误。

一个可能的解决方案是定义一个:

public class SynchronizedDictionary<K: Hashable, V> { ... }

这是一个引用类型,下标 setter 不再改变 syncDict 变量(它现在是指向实际对象存储的“指针”)。通过该更改,您的代码可以正常运行。

关于swift - Xcode 错误地报告 Swift Access 竞争条件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54998659/

相关文章:

swift - 从 UIStackView 中移除 UIView 会改变它的大小

ios - Controller 在应用程序访问之前就已初始化

ios - 查询语句未在 xcode 中执行 sqlite 数据库

c++ - moveToThread 与从 Qt 中的 QThread 派生

c++ - 使用 std::atomic 标志和 std::condition_variable 等待工作线程

swift - 在 OS X swift 3.0 中设置删除文件的权限

ios - 将图像数组添加到 Realm 数据库

swift - 在消息表单上取消后崩溃

iphone - 更改 iOS 中的地理位置权限通知文本

java - 在Java中,我可以依靠引用分配是原子的来实现写时复制吗?