在xcode 8.3上使用swift 3.1,使用线程消毒剂运行以下代码会发现一个数据争用(请参见代码中的写入和读取注释):

  private func incrementAsync() {
    let item = DispatchWorkItem { [weak self] in
      guard let strongSelf = self else { return }
      strongSelf.x += 1 // <--- the write

      // Uncomment following line and there's no race, probably because print introduces a barrier
      //print("> DispatchWorkItem done")
    }
    item.notify(queue: .main) { [weak self] in
      guard let strongSelf = self else { return }
      print("> \(strongSelf.x)") // <--- the read
    }

    DispatchQueue.global(qos: .background).async(execute: item)
  }

这对我来说似乎很奇怪,因为DispatchWorkItem的文档提到它允许:
收到完成通知
这意味着一旦完成工作项的执行,就会调用notify回调。
因此,我希望在工作结束和通知结束之间存在一种关系。如果有的话,用这样一个注册的回调函数使用一个不触发线程消毒剂错误的happens-before的正确方法是什么?
我尝试将DispatchWorkItem注册到DispatchWorkItem中,但争用仍然存在(可能是因为该标志只适用于同一队列,所以文档中的notify标志所做的工作比较少)。但即使在与工作项执行相同的(后台)队列上调用notify,也会导致争用。
如果你想尝试一下,我在Github上发布了完整的xcode项目:https://github.com/mna/TestDispatchNotify
有一个不使用TSAN构建应用程序的notify方案,以及激活线程消毒剂的item.notify(flags: .barrier, queue: .main) ...方案。
谢谢,
马丁

最佳答案

编辑(2019-01-07):正如Rob在评论中提到的,这不能用XCODE/FULL的最新版本来重现(我没有安装XCODE,我猜不出版本号)。不需要解决方法。
看来我发现了。使用DispatchGroup.notify在组的已调度项完成时获得通知,而不是使用DispatchWorkItem.notify避免数据争用。以下是没有数据竞争的同一个ISH片段:

  private func incrementAsync() {
    let queue = DispatchQueue.global(qos: .background)

    let item = DispatchWorkItem { [weak self] in
      guard let strongSelf = self else { return }
      strongSelf.x += 1
    }

    let group = DispatchGroup()
    group.notify(queue: .main) { [weak self] in
      guard let strongSelf = self else { return }
      print("> \(strongSelf.x)")
    }
    queue.async(group: group, execute: item)
  }

因此,DispatchGroup引入了一个在关系之前发生的事件,并且在线程(在本例中是单个异步工作项)完成执行后安全地调用了notify,而DispatchWorkItem.notify不提供此保证。

关于swift - 如何使用GCD DispatchWorkItem.notify避免数据竞争?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/43131084/

10-12 14:43