在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
回调。
所以我希望happens-before
的工作结束与其通知结束之间存在DispatchWorkItem
关系。使用带有注册DispatchWorkItem
回调的notify
以及不会触发Thread Sanitizer错误的正确方法(如果有的话)是什么?
我尝试使用notify
注册item.notify(flags: .barrier, queue: .main) ...
,但竞争仍然存在(可能是因为该标志仅适用于同一队列,文档在.barrier
标志所做的事情上很少)。但即使使用flags: .barrier
在与工作项执行相同的(后台)队列上调用notify,也会导致竞争。
如果您想尝试一下,我在github上发布了完整的XCode项目:https://github.com/mna/TestDispatchNotify
有一个TestDispatchNotify
方案可以在没有tsan的情况下构建应用程序,而TestDispatchNotify+Tsan
则会激活Thread Sanitizer。
谢谢, 马丁
答案 0 :(得分:1)
编辑(2019-01-07):正如@Rob在对该问题的评论中提到的那样,不能再使用最新版本的Xcode / Foundation(我不喜欢#39;已经安装了Xcode,我不会猜测版本号)。没有必要的解决方法。
好像我发现了。使用DispatchGroup.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
没有&#39} ;提供这种保证。
答案 1 :(得分: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
您绝对正确! 增加最后期限...
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 0.01, execute: job)
你已经
job notify
即使作业永远不会执行
通知与DispatchWorkItem的闭包捕获的任何数据的同步无关。
让我们使用DispatchGroup尝试这个示例!
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let group = DispatchGroup()
group.notify(queue: .main) {
print("group notify")
}
并查看结果
group notify
!!! WTF !!!您是否仍然认为自己已在代码中解决了比赛? 要同步任何读取,写入...,请使用串行队列,屏障或信号量。调度组是完全不同的野兽:-)使用调度组,您可以将多个任务组合在一起,等待它们完成或在完成后收到通知。
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