您提出了正确的问题,但我认为您有些困惑(主要是由于互联网上有关此主题的帖子不太清楚)。
并发/串行
让我们看看如何创建一个新的调度队列:
let serialQueue = DispatchQueue(label: label)
如果您不指定任何其他附加参数,则此队列将表现为串行队列:这意味着在此队列上调度的每个块(同步或异步无关紧要)将单独执行,而没有其他块的可能性在同一个队列上同时执行。
这并不意味着其他任何东西都停止了,它只是意味着如果在同一个队列上调度了其他东西,它将等待第一个块完成后再开始执行。其他线程和队列仍将自行运行。
但是,您可以创建一个并发队列,它不会以这种方式约束这些代码块,相反,如果同时在同一个队列上调度更多代码块,它将在同时(在不同的线程上)
let concurrentQueue = DispatchQueue(label: label,
qos: .background,
attributes: .concurrent,
autoreleaseFrequency: .inherit,
target: .global())
因此,您只需将属性传递concurrent
给队列,它就不再是串行的了。
(我不会谈论其他参数,因为它们不是这个特定问题的焦点,我认为,您可以在评论中链接的其他 SO 帖子中阅读它们,或者,如果还不够,您可以问另一个问题)
如果您想了解有关并发队列的更多信息(又名:如果您不关心并发队列,请跳过)
你可能会问:我什么时候需要并发队列?
好吧,举个例子,让我们考虑一个您想要在共享资源上同步 READS 的用例:由于读取可以同时完成而不会出现问题,因此您可以为此使用并发队列。
但是如果你想在那个共享资源上写呢?好吧,在这种情况下,写入需要充当“屏障”,并且在执行该写入期间,没有其他写入和读取可以同时对该资源进行操作。为了获得这种行为,快速代码看起来像这样
concurrentQueue.async(flags: .barrier, execute: { /*your barriered block*/ })
因此,换句话说,您可以将并发队列临时作为串行队列工作,以备不时之需。
再一次,并发/串行的区别仅对分派到同一队列的块有效,它与可以在另一个线程/队列上完成的其他并发或串行工作无关。
同步/异步
这完全是另一个问题,与前一个问题几乎没有任何联系。
这两种分派代码块的方法与您在分派调用时所处的当前线程/队列有关。在执行您在另一个队列上分派的代码时,此调度调用会阻塞(在同步的情况下)或不阻塞(异步)该线程/队列的执行。
因此,假设我正在执行一个方法,并且在该方法中我在其他队列上调度异步某些东西(我正在使用主队列,但它可以是任何队列):
func someMethod() {
var aString = "1"
DispatchQueue.main.async {
aString = "2"
}
print(aString)
}
发生的情况是,这段代码被分派到另一个队列上,并且可以在该队列上串行或并发执行,但这与当前队列(调用 someMethod 的队列)上发生的事情无关。
当前队列上发生的情况是代码将继续执行,并且在打印该变量之前不会等待该块完成。这意味着,您很可能会看到它打印 1 而不是 2。(更准确地说,您不知道首先会发生什么)
相反,如果您将其同步调度,那么您将始终打印 2 而不是 1,因为当前队列会等待该代码块完成,然后再继续执行。
所以这将打印 2:
func someMethod() {
var aString = "1"
DispatchQueue.main.sync {
aString = "2"
}
print(aString)
}
但这是否意味着调用 someMethod 的队列实际上已停止?
好吧,这取决于当前队列:
- 如果是连续的,那就是。先前分派到该队列或将在该队列上分派的所有块都必须等待该块完成。
- 如果它是并发的,那就不是。所有并发块将继续执行,只有这个特定的执行块会被阻塞,等待这个调度调用完成它的工作。当然,如果我们处于屏障的情况下,那么它就像串行队列一样。
当 currentQueue 和我们调度的队列相同时会发生什么?
假设我们在串行队列上(我认为这将是您的大部分用例)
- 如果我们调度同步,而不是死锁。该队列将不再执行任何操作。这是可能发生的最糟糕的情况。
- 如果我们调度异步,那么代码将在该队列上已经调度的所有代码的末尾执行(包括但不限于现在在 someMethod 中执行的代码)
因此,当您使用同步方法时要格外小心,并确保您不在您正在分派的同一个队列中。
我希望这能让你更好地理解。