3

在 XCode 8.3 上使用 Swift 3.1,使用 Thread Sanitizer 运行以下代码会发现数据竞争(请参阅代码中的写入和读取注释):

  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一旦工作项的执行完成,就会调用回调。

所以我希望's 的工作关闭和它的通知关闭之间会有happens-before关系。DispatchWorkItem将 aDispatchWorkItem与这样的注册notify回调一起使用不会触发 Thread Sanitizer 错误的正确方法是什么(如果有的话)?

我尝试注册notifywithitem.notify(flags: .barrier, queue: .main) ...但比赛仍然存在(可能是因为该标志仅适用于同一个队列,有关该标志的作用的文档很少.barrier)。但是,即使在与工作项的执行相同的(后台)队列上调用 notify ,也会flags: .barrier导致竞争。

如果你想试试这个,我在 github 上发布了完整的 XCode 项目:https ://github.com/mna/TestDispatchNotify

有一个TestDispatchNotify方案可以在没有 tsan 的情况下构建应用程序,并TestDispatchNotify+Tsan激活 Thread Sanitizer。

谢谢,马丁

4

2 回答 2

1

编辑(2019-01-07):正如@Rob 在对该问题的评论中所提到的,这不能再用最新版本的 Xcode/Foundation 复制(我不再安装 Xcode,我不会猜测一个版本号)。不需要解决方法。


好吧,看来我发现了。使用 aDispatchGroup.notify在组的分派项目完成时收到通知,而不是DispatchWorkItem.notify,避免数据竞争。这是没有数据竞争的相同片段:

  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不提供此保证。

于 2017-03-31T02:00:15.083 回答
0
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

var job = DispatchWorkItem {
    for i in 0..<3 {
        DispatchQueue.main.async {
            print("job", i)
        }
    }
    DispatchQueue.main.async {
        print("job done")
    }
}
job.notify(queue: .main) {
    print("job notify")
}

DispatchQueue.global(qos: .background).asyncAfter(deadline: .now(), execute: job)
usleep(100)
job.cancel()

如果你猜这个片段会打印出来

job 0
job 1
job 2
job done
job notify

你是绝对正确的!增加一个deadLine ...

DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 0.01, execute: job)

你有

job notify

即使作业从未执行

notify 与 DispatchWorkItem 的闭包捕获的任何数据的同步无关。

让我们用 DispatchGroup 试试这个例子!

import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true


let group = DispatchGroup()
group.notify(queue: .main) {
    print("group notify")
}

并查看结果

group notify

!!!哇!你仍然认为你解决了代码中的竞争吗? 要同步任何读、写……使用串行队列、屏障或信号量。调度组是完全不同的野兽 :-) 使用调度组,您可以将多个任务组合在一起,等待它们完成或在它们完成后收到通知。

import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

let job1 = DispatchWorkItem {
    sleep(1)
    DispatchQueue.main.async {
        print("job 1 done")
    }
}
let job2 = DispatchWorkItem {
    sleep(2)
    DispatchQueue.main.async {
        print("job 2 done")
    }
}
let group = DispatchGroup()
DispatchQueue.global(qos: .background).async(group: group, execute: job1)
DispatchQueue.global(qos: .background).async(group: group, execute: job2)

print("line1")
group.notify(queue: .main) {
    print("group notify")
}
print("line2")

印刷

line1
line2
job 1 done
job 2 done
group notify
于 2019-01-06T14:39:38.867 回答