我刚刚开始在Swift编程语言中学习更多有关Grand Central Dispatch的信息。
我遵循在线教程,以更好地了解GCD并尝试了各种用法的示例...
在有关工作项目的部分中,我编写了以下代码:
func useWorkItem() {
var value = 10
let workItem = DispatchWorkItem {
value += 5
}
workItem.perform()
let queue = DispatchQueue.global(qos: .utility)
queue.async(execute: workItem)
workItem.notify(queue: DispatchQueue.main) {
print("value = ", value)
}
}
代码基本上以两个不同的队列(主要和全局队列)执行工作元素,当工作项在两个队列中运行时,我都会得到结果。
上面代码的输出为:20。
当我尝试稍微操纵代码并在混合物中添加另一个队列时,并使用与全局队列相同的qos
进行了相同的工作列表(.utility
),例如:
func useWorkItem() {
var value = 10
let workItem = DispatchWorkItem {
value += 5
}
workItem.perform()
let queue = DispatchQueue.global(qos: .utility)
queue.async(execute: workItem)
let que = DispatchQueue(label: "com.appcoda.delayqueue1", qos: .utility)
que.async(execute: workItem)
workItem.notify(queue: DispatchQueue.main) {
print("value = ", value)
}
}
应用程序崩溃。
但是,当我更改命令的顺序时,我将workItem.notify
方法移动到方法的开头时,应用程序可以正常工作并给我正确的输出,即25:
func useWorkItem() {
var value = 10
let workItem = DispatchWorkItem {
value += 5
}
workItem.notify(queue: DispatchQueue.main) {
print("value = ", value)
}
workItem.perform()
let queue = DispatchQueue.global(qos: .utility)
queue.async(execute: workItem)
let que = DispatchQueue(label: "com.appcoda.delayqueue1", qos: .utility)
que.async(execute: workItem)
}
任何人都可以帮助了解.notify()
方法如何真正起作用吗?为什么命令的顺序有所作为?
预先感谢...
您共享的第一个示例(我收集的直接来自教程)的编写不正确,原因有两个:
-
它正在更新多个线程的变量。这是一个固有的非线程安全过程。事实证明,由于这里不值得概述的原因,从技术上讲,这不是作者原始示例中的问题,但它是一种非常脆弱的设计,这是由您随后的示例中迅速表现出来的非线程安全行为所说明的。
。如果从多个线程进行操作,则应始终同步对变量的访问。您可以为此使用专用的串行队列,
NSLock
,读取器图案或其他模式。虽然我经常使用另一个GCD队列进行同步,但我认为当我们专注于DispatchWorkItem
在各种队列上的GCD行为时,这会令人困惑,因此在下面的示例中,我将使用NSLock
同步访问,访问,访问,完成后尝试使用value
和unlock
之前,请致电lock()
。 -
您说第一个示例显示" 20"。这仅仅是时机的偶然性。如果您更改为...
let workItem = DispatchWorkItem { os_log("starting") Thread.sleep(forTimeInterval: 2) value += 5 os_log("done") }
...然后它可能会说" 15",而不是" 20",因为在
async
呼叫到全局队列之前,您会看到notify
的CC_11。现在,您永远不会在真实应用中使用sleep
,但我将其放入说明时间问题。底线,
DispatchWorkItem
上的notify
首先完成时发生,并且不会等待其随后的调用。此代码需要在您的notify
块和您派遣到该全局队列的电话之间所谓的"种族条件",并且您不保证将首先运行。 -
个人,即使搁置了种族条件和从多个线程突变一些变量的固有的非线程安全行为,我建议不要多次调用相同的
DispatchWorkItem
,至少在此上与notify
结合工作项目。 -
如果您想在完成所有操作后要进行通知,则应使用
DispatchGroup
,而不是单个DispatchWorkItem
上的notify
。
将所有这些拉在一起,您会得到类似的东西:
import os.log
var value = 10
let lock = NSLock() // a lock to synchronize our access to `value`
func notifyExperiment() {
// rather than using `DispatchWorkItem`, a reference type, and invoking it multiple times,
// let's just define some closure or function to run some task
func performTask(message: String) {
os_log("starting %@", message)
Thread.sleep(forTimeInterval: 2) // we wouldn't do this in production app, but lets do it here for pedagogic purposes, slowing it down enough so we can see what's going on
lock.lock()
value += 5
lock.unlock()
os_log("done %@", message)
}
// create a dispatch group to keep track of when these tasks are done
let group = DispatchGroup()
// let's enter the group so that we don't have race condition between dispatching tasks
// to the queues and our notify process
group.enter()
// define what notification will be done when the task is done
group.notify(queue: .main) {
self.lock.lock()
os_log("value = %d", self.value)
self.lock.unlock()
}
// Let's run our task once on the global queue
DispatchQueue.global(qos: .utility).async(group: group) {
performTask(message: "from global queue")
}
// Let's run our task also on a custom queue
let customQueue = DispatchQueue(label: "com.appcoda.delayqueue1", qos: .utility)
customQueue.async(group: group) {
performTask(message: "from custom queue")
}
// Now let's leave the group, resolving our `enter` at the top, allowing the `notify` block
// to run iff (a) all `enter` calls are balanced with `leave` calls; and (b) once the `async(group:)`
// calls are done.
group.leave()
}