在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/