在XCode 8.3上使用SWIFT3.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的工作结束和通知关闭之间会存在happens-before关系。如果有正确的方法,那么使用DispatchWorkItem并使用这样一个注册的notify回调不会触发线程清除器错误的方法是什么?
我试着用notify注册item.notify(flags: .barrier, queue: .main) ...,但是竞争仍然存在(可能是因为标记只适用于同一个队列,文档很少显示.barrier标志的功能)。但是,即使使用flags: .barrier在同一个(后台)队列上调用notify作为工作项的执行,也会导致竞争。
如果您想尝试这一点,我在github上发布了完整的XCode项目:https://github.com/mna/TestDispatchNotify
这里有一个TestDispatchNotify计划,在没有tsan的情况下构建应用程序,而使用线程杀菌剂的TestDispatchNotify+Tsan则是激活的。
谢谢,马丁
发布于 2017-03-31 02:00:15
编辑(2019-01-07):正如@Rob在关于这个问题的评论中提到的那样,不能再用Xcode/Foundation的最新版本来复制它了(我不再安装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没有提供这种保证。
发布于 2019-01-06 14:39:38
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 notifyhttps://stackoverflow.com/questions/43131084
复制相似问题