【问题标题】:Avoid Data Race condition in swift迅速避免数据竞争条件
【发布时间】:2018-08-31 05:47:42
【问题描述】:

当我运行 TSan 工具时,我的代码中出现了竞争条件。由于同时从不同的队列和线程访问了相同的代码,这就是为什么我不能使用串行队列或屏障,因为队列只会阻止访问共享资源的单个队列而不是其他队列。

我使用objc_sync_enter(object) | objc_sync_exit(object) 和锁定NSLock() or NSRecursiveLock() 来保护共享资源,但这些也不起作用。

当我在 Objective C 中使用 @synchronized() 关键字来保护共享资源时,它可以正常工作,并且在特定代码块中我没有遇到竞争条件。

那么,由于我们不能在 Swift 语言中使用 @synchronized() 关键字,因此在 Swift 中保护数据的替代方法是什么。

PFA 截图供参考 -

【问题讨论】:

  • self.moments 还有哪些地方被访问了?
  • 已经从不同的类访问过。
  • 尝试发送一些标志:D

标签: ios swift concurrency


【解决方案1】:

我不明白“我不能使用串行队列或屏障,因为队列只会阻止访问共享资源的单个队列而不是其他队列。”使用队列是这个问题的标准解决方案。

class MultiAccess {
    private var _property: String = ""
    private let queue = DispatchQueue(label: "MultiAccess")
    var property: String {
        get {
            var result: String!
            queue.sync {
                result = self._property
            }
            return result
        }
        set {
            queue.async {
                self._property = newValue
            }
        }
    }
}

通过这种结构,对property 的访问是原子的和线程安全的,调用者无需做任何特殊的事情。请注意,这有意为类使用单个队列,而不是每个属性的队列。通常,您希望相对较少数量的队列做大量工作,而不是大量队列做少量工作。如果你发现你正在从许多不同的线程访问一个可变对象,你需要重新考虑你的系统(可能减少线程的数量)。没有一种简单的模式可以让您无需仔细考虑您的具体用例就可以高效、安全地工作。

但是这种结构对于系统框架可能会以最小的争用在随机线程上调用您的问题很有用。它很容易实现并且相当容易正确使用。如果您有更复杂的问题,则必须为该问题设计解决方案。


编辑:我很久没有想到这个答案了,但是 Brennan 的 cmets 让我重新注意到它。由于我在原始代码中的错误,我的原始答案是好的,但如果你修复了错误,那就糟糕了。 (如果您想查看我使用屏障的原始代码,请查看编辑历史记录,我不想把它放在这里,因为人们会复制它。)我已将其更改为使用标准串行队列而不是并发队列。

如果没有仔细考虑线程将如何生成,请不要生成并发队列。如果您要同时进行许多访问,那么您将创建很多线程,这很糟糕。如果您不会有很多同时访问,那么您不需要并发队列。 GCD 会谈对管理线程做出了承诺,但实际上并没有做到这一点。你绝对可以得到线程爆炸(正如 Brennan 提到的那样。)

【讨论】:

  • 我尝试了相同的方法来修复它。但是,当我们在一个类中有多个属性时,这是正确的方法吗?
  • 是的。在许多情况下,为整个类设置一个队列而不是每个属性一个队列是合理的。这取决于性能要求。请记住,如果多个值是可变的,则可能会由于一个属性的更改与其他属性的更改不同步而产生额外的竞争条件。无论如何,这是正确的基础;精确的实现取决于你需要什么样的原子行为。
  • 是的。因为其他方法在我的情况下不起作用。
  • @RobNapier 由于线程爆炸,我不得不在属性中消除这种模式。注意:文本提到了一个并发队列,而代码显示了一个串行队列,所以我认为代码是错误的,因为使用了屏障标志。发生的情况是设置了一个值,然后通知许多观察者,然后在异步工作仍未完成时立即调用 getter。我发现这会导致每个观察者的许多线程被阻塞。现在我完全避免在属性中使用这种模式。我相信一旦您在设置属性后移至下一行,就应该设置值。
  • @Brennan “发生的情况是设置了一个值,然后通知许多观察者,然后在异步工作仍未完成时立即调用 getter。”这很可能是问题所在。我会重新设计,让观察者收到新的价值,而不仅仅是“改变了一些东西”。感谢您发现缺少 .concurrent 错误。这让我重新思考了这个答案,我已经对其进行了编辑。
猜你喜欢
  • 1970-01-01
  • 2015-01-30
  • 2010-09-25
  • 2010-09-25
  • 2019-06-12
  • 1970-01-01
  • 2020-01-16
  • 2014-04-02
相关资源
最近更新 更多