2

我刚刚开始在 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()方法的实际工作原理吗?为什么命令的顺序有所不同?

非常感谢提前...

4

1 回答 1

2

你分享的第一个例子(我直接从教程中收集)写得不好有几个原因:

  1. 它正在从多个线程更新一个变量。那是一个本质上非线程安全的过程。事实证明,由于不值得在这里概述的原因,这在作者的原始示例中在技术上不是问题,但它是一个非常脆弱的设计,在您的后续示例中迅速表现出来的非线程安全行为说明了这一点。

    如果从多个线程操作变量,则应始终同步对变量的访问。您可以为此使用专用的串行队列NSLock、读写器模式或其他模式。虽然我经常使用另一个 GCD 队列进行同步,但我认为当我们关注DispatchWorkItem各种队列的 GCD 行为时,这会让人感到困惑,所以在下面的示例中,我将使用NSLock同步访问,调用lock()之前我尝试使用valueunlock当我完成时。

  2. 您说第一个示例显示“20”。这只是时间上的意外。如果你把它改成...

    let workItem = DispatchWorkItem {
        os_log("starting")
        Thread.sleep(forTimeInterval: 2)
        value += 5
        os_log("done")
    }
    

    ...然后它可能会说“15”,而不是“20”,因为在完成对全局队列的调用之前,您会看到notifyfor the 。现在,你永远不会在真正的应用程序中使用它,但我把它放进去来说明时间问题。workItem.perform()asyncsleep

    底线,notifyon aDispatchWorkItem发生在调度工作项第一次完成时,它不会等待它的后续调用。此代码在您的块和您分派到该全局队列的调用之间需要所谓的“竞争条件”,您notify不确定哪个会先运行。

  3. 就个人而言,即使抛开竞争条件和从多个线程中改变某些变量的固有的非线程安全行为,我建议不要DispatchWorkItem多次调用相同的,至少与notify该工作项一起。

  4. 如果您想在一切完成后发出通知,您应该在个人上使用 a DispatchGroup,而不是 a 。notifyDispatchWorkItem

把这一切放在一起,你会得到类似的东西:

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()
}
于 2017-12-22T17:58:42.900 回答