Xcode 错误地报告 Swift 访问竞争条件

Posted

技术标签:

【中文标题】Xcode 错误地报告 Swift 访问竞争条件【英文标题】:Xcode Incorrectly Reporting Swift Access Race Condition 【发布时间】: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。 【参考方案1】:

线程清理器向

报告Swift access race
var syncDict = SynchronizedDictionary<String, String>()

结构,因为在

有一个变异访问(通过下标设置器)
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 
        
    

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

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

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

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

【讨论】:

啊,所以这是变量 syncDict 的竞争条件,这与 syncDict 中的任何内容都无关 :)

以上是关于Xcode 错误地报告 Swift 访问竞争条件的主要内容,如果未能解决你的问题,请参考以下文章

Xcode 错误地设置为使用旧版构建系统

Swift NSDate Xcode 错误 - SourceKitService 终止

Swift 中的 Xcode 调试/崩溃报告是不是损坏?

条件竞争漏洞

无法使用 swift 访问 tabBar 项的索引

在 XCode、iOS 开发中编译时在 Keychain.swift 中构建错误