【问题标题】:Swift - How to prevent DispatchQueues from executing on the same thread?Swift - 如何防止 DispatchQueues 在同一个线程上执行?
【发布时间】:2019-07-12 06:41:29
【问题描述】:

我最近一直在学习线程和 DispatchQueues,并且遇到了一个大问题。我多次听说 GCD不保证给定的工作块可以在哪个线程上执行。大多数时候,这是一个有用的层次抽象。但是,我遇到了一个我仍然不知道原因的错误,但这让我意识到在我看来 GCD 这方面的潜在缺陷是什么。

例子:

let queue1 = DispatchQueue(label: "one")
let queue2 = DispatchQueue(label: "two")
queue1.sync {
    let importantValue1 = "importantValue1"
    let importantValue2 = queue2.sync {
        return "importantValue2"
    }
    print("did important work, got values", importantValue1, importantValue2)
}

我的问题是,我是否至少可以保证我的队列不会在同一个线程上执行?从我所见,我似乎没有这个保证。但是,没有它,我不是一直处于僵局的危险之中吗?在上面的例子中,如果两个队列都在线程 7 上执行会发生什么?调用queue2.sync 不会导致应用崩溃吗?

【问题讨论】:

  • 就像 Rob N 说的,当你使用sync 时,作为优化 GCD 会尽可能在当前线程上运行它。如果你使用async,你会发现它很可能会出现在另一个线程上。最重要的是,不,您不会一直处于僵局的危险之中。使用你这里的模式,它肯定不会死锁,但它会很聪明地使用最好的可用工作线程,如果可以的话避免上下文切换。

标签: swift concurrency grand-central-dispatch deadlock dispatch-queue


【解决方案1】:

在大多数情况下,我会期望这两个块在同一个队列上运行。其实我们看看:

import Foundation

let queue1 = DispatchQueue(label: "one")
let queue2 = DispatchQueue(label: "two")
queue1.sync {
    let importantValue1 = "importantValue1"
    print(Thread.current)  // Print the current queue
    let importantValue2: String = queue2.sync {
        print(Thread.current) // Print the current queue
        return "importantValue2"
    }
    print("did important work, got values", importantValue1, importantValue2)
}

<NSThread: 0x6000023b2900>{number = 1, name = main}
<NSThread: 0x6000023b2900>{number = 1, name = main}
did important work, got values importantValue1 importantValue2

是的,在我的示例中,它们都在主线程上运行,正如您通常希望它们那样。在调用.sync 时,通常没有理由强加切换线程的大量成本。当前线程在块完成之前不能做任何事情,所以它也可以在当前线程上运行该块,只要没有任何限制(例如,提交到主队列的块必须 在主线程上运行)。

你是对的,如果你不小心,这可能会产生死锁,但这不是因为使用的线程。死锁是循环使用.sync 所固有的。无论使用什么底层线程,队列仍然必须按特定顺序调度块,这就是最常造成死锁的原因。

【讨论】:

  • 你的句子“当前线程在块完成之前不能做任何事情,所以它还不如在当前线程上运行那个块......”是我感到困惑的地方。当一个线程由于sync而被阻塞时,它仍然能够从其他调度队列中完成工作,而不是阻塞它的那个?
  • 不是来自随机的其他队列,但它可以从它正在等待的队列(它称为 sync 的队列)中运行事物。但是,如果您深入思考正在运行的线程(而不是调度到哪些队列块),那么您可能会遇到麻烦.想想队列和它们的依赖图。将线程留给操作系统。线程是关于并行性的。队列是关于并发的,你通常应该关注并发。
【解决方案2】:

后台队列中的任务永远不会阻止后台队列中的其他任务运行,因此虽然您可能会遇到死锁,但在同一线程上执行的队列不会导致死锁。

另一方面,在另一个队列上同步运行代码是毫无意义的。

【讨论】:

  • 我这样做的原因是因为在我的真实代码中我使用的是 Core Data 并且需要在对象上下文的队列上执行与对象上下文的交互,但我想同步地进行
  • Core Data 的替代方案是通过对象的 NSManagedObjectID 从当前队列的上下文中重新获取对象。你是否愿意这样做取决于你是否已经在使用多个 NSManagedObjectContexts 并且被证明对性能更好——但在这里注意这是可能的。 :)
  • “在另一个队列上同步运行代码是毫无意义的。”这种说法是错误的。您想要这样做的原因有很多。例如,它可用于在访问共享资源时避免竞争条件。
猜你喜欢
  • 1970-01-01
  • 2011-06-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-06
相关资源
最近更新 更多