Swift 如何避免与GCD DispatchWorkItem.notify的数据竞争?
对于XCode 8.3上的Swift 3.1,使用线程消毒剂运行以下代码会发现数据竞争(请参阅代码中的写和读注释):Swift 如何避免与GCD DispatchWorkItem.notify的数据竞争?,swift,grand-central-dispatch,thread-sanitizer,Swift,Grand Central Dispatch,Thread Sanitizer,对于XCode 8.3上的Swift 3.1,使用线程消毒剂运行以下代码会发现数据竞争(请参阅代码中的写和读注释): private func incrementAsync(){ 让item=DispatchWorkItem{[weak self]在 guard let strongSelf=self-else{return} strongSelf.x+=1//编辑(2019-01-07):正如@Rob在对该问题的评论中所提到的,这一点在最新版本的Xcode/Foundation中无法复制(我不
private func incrementAsync(){
让item=DispatchWorkItem{[weak self]在
guard let strongSelf=self-else{return}
strongSelf.x+=1//编辑(2019-01-07):正如@Rob在对该问题的评论中所提到的,这一点在最新版本的Xcode/Foundation中无法复制(我不再安装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
引入了一个before关系,并且在线程(在本例中是单个异步工作项)完成执行后安全地调用了notify
,而DispatchWorkItem.notify
不提供这种保证
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()
你完全正确!
增加最后期限
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
!!!WTF!!!你还认为你在代码中解决了这场竞赛吗?
要同步任何读、写…请使用串行队列、屏障或信号量。调度组与beast:-)完全不同。您可以将多个任务分组,等待它们完成,或者在它们完成后接收通知
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
DispatchWorkItem
应保证单次执行不存在争用。针对您的情况的争用可能来自多次执行块,然后在单独的队列上发出通知(与工作队列不同步).如果您还没有看到,我认为dispatch\u block\u notify()文档中对该行为的解释
更好。不过,它是一次执行。每次调用该方法时,它都会创建一个新的DispatchWorkItem,只执行一次。感谢您对文档的介绍,它确实提到了使用DispatchGroup!看起来swift文档在某些地方还不太流行。嗯,是的,您完全正确。我把自己搞糊涂了仔细想想。@mna请看我的“答案”。这不是关于线程安全数据的读写。我只是想解释一下为什么你错了,为什么你的回答不应该作为解决方案被接受。这一点已经无法体现。他们一直在修改和增强线程消毒剂,所以我怀疑这仅仅是TSAN早期迭代中的一个错误。
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