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

Pel*_*tau 5 xcode multithreading race-condition swift ios-multithreading

我相信 XCode 错误地报告了我的 Swift Access Race SynchronizedDictionary- 或者是这样?

我的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
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

以下测试代码将触发“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)
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

...但这已经发生在SynchronizedDictionary- 那么为什么 Xcode 报告访问竞争条件?- 是 Xcode 有问题,还是我遗漏了什么?

Mar*_*n R 5

线程清理程序报告Swift 访问竞争

\n\n
var syncDict = SynchronizedDictionary<String, String>()\n
Run Code Online (Sandbox Code Playgroud)\n\n

结构,因为有一个变异访问(通过下标设置器)

\n\n
syncDict["\\(i)"] = "\\(i)"\n
Run Code Online (Sandbox Code Playgroud)\n\n

从一个线程,以及对同一结构的只读访问(通过下标 getter)

\n\n
_ = syncDict["\\(i)"]\n
Run Code Online (Sandbox Code Playgroud)\n\n

来自不同的线程,没有同步。

\n\n

这与对属性的冲突访问无关private var dictionary,也与下标方法内发生的情况无关。如果将结构简化为,您将获得相同的 \xe2\x80\x9cSwift access race\xe2\x80\x9d

\n\n
public struct SynchronizedDictionary<K: Hashable, V> {\n    private let dummy = 1\n\n    public subscript(key: String) -> String {\n        get {\n            return key\n        }\n        set {\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

所以这是一个正确的报告,而不是错误。

\n\n

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

\n\n
public class SynchronizedDictionary<K: Hashable, V> { ... }\n
Run Code Online (Sandbox Code Playgroud)\n\n

这是一个引用类型,下标设置器不再改变syncDict(现在是 \xe2\x80\x9cpointer\xe2\x80\x9d 到实际对象存储中)。进行此更改后,您的代码将不会出现错误。

\n