Swift DispatchQueue:串行或并行
Swift DispatchQueue: Serial or parallel
在我的应用程序中,我必须同时在后台解压缩多个文件。哪个代码会在多个线程上并行执行 compressedFiles 数组:
for file in compressedFiles {
DispatchQueue.global(qos: .userInteractive).async {
let work = DispatchGroup()
work.enter()
file.decompress()
work.leave()
}
}
或:
DispatchQueue.global(qos: .userInteractive).async {
for file in compressedFiles {
let work = DispatchGroup()
work.enter()
file.decompress()
work.leave()
}
}
此外,如果我想在其中一个文件解压过程完成后得到通知,如何利用 DispatchGroup class? wait() 和 notify() 放在哪里?
谢谢。
调度组需要在循环之外,每个 enter
需要在循环内但在包含 leave
的线程之外。但是整个代码也需要在它自己的调度队列中,因为你不能在主队列上阻塞(等待)。
let queue = DispatchQueue(label:"myqueue")
queue.async {
let work = DispatchGroup()
for file in compressedFiles {
work.enter()
DispatchQueue.global(qos: .userInteractive).async {
file.decompress()
work.leave()
}
}
work.notify... // get on main thread here?
}
您的第二个示例将 运行 按顺序排列它们。它正在执行一次调度,运行一个接一个地处理它们。您的第一个示例将 运行 它们并行,将每个分配给不同的工作线程。不幸的是,两者都没有正确使用调度组。
关于调度组,您应该在循环之前定义它,并且在调用 async
之前 enter
定义它。但仅当您从 async
调用中调用异步进程时,才需要手动调用 enter
和 leave
。但考虑到 decompress
可能是一个同步过程,您只需将组提供给 async
,它将为您处理所有事情:
let group = DispatchGroup()
for file in compressedFiles {
DispatchQueue.global(qos: .userInteractive).async(group: group) {
file.decompress()
}
}
group.notify(queue: .main) {
// all done
}
但是与其担心调度组逻辑,并行示例中还有更深层次的问题。具体来说,它会遭受线程爆炸的困扰,它可能会超过 CPU 上的可用内核数。更糟糕的是,如果您有很多文件要解压缩,您甚至可以超过 GCD 在其池中的工作线程数量限制。当发生这种情况时,它可以阻止 运行 在 GCD 上针对该 QoS 进行任何其他操作。相反,您想 运行 它并行,但您希望将它限制在合理的并发度,同时仍然享受并行性,以避免耗尽其他任务的资源。
如果您希望它 运行 并行,但又要避免线程爆炸,通常会达到 concurrentPerform
。这提供了 CPU 支持的最大并行度,但防止了线程爆炸可能导致的问题:
DispatchQueue.global(qos: .userInitiated).async {
DispatchQueue.concurrentPerform(iterations: compressedFiles.count) { index in
compressedFiles[index].decompress()
}
DispatchQueue.main.async {
// all done
}
}
这会将并行度限制为设备内核允许的最大值。它还消除了派遣组的需要。
或者,如果您想享受并行性,但并发度较低(例如,为其他任务留出一些核心,以尽量减少峰值内存使用等),您可以使用操作队列和 maxConcurrentOperationCount
:
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 4 // a max of 4 decompress tasks at a time
let completion = BlockOperation {
// all done
}
for file in compressedFiles {
let operation = BlockOperation {
file.decompress()
}
completion.addDependency(operation)
queue.addOperation(operation)
}
OperationQueue.main.addOperation(completion)
或者马特指出,在 iOS 13(或 macOS 10.15)及更高版本中,您可以:
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 4
for file in compressedFiles {
queue.addOperation {
file.decompress()
}
}
queue.addBarrierBlock {
DispatchQueue.main.async {
// all done
}
}
在我的应用程序中,我必须同时在后台解压缩多个文件。哪个代码会在多个线程上并行执行 compressedFiles 数组:
for file in compressedFiles {
DispatchQueue.global(qos: .userInteractive).async {
let work = DispatchGroup()
work.enter()
file.decompress()
work.leave()
}
}
或:
DispatchQueue.global(qos: .userInteractive).async {
for file in compressedFiles {
let work = DispatchGroup()
work.enter()
file.decompress()
work.leave()
}
}
此外,如果我想在其中一个文件解压过程完成后得到通知,如何利用 DispatchGroup class? wait() 和 notify() 放在哪里?
谢谢。
调度组需要在循环之外,每个 enter
需要在循环内但在包含 leave
的线程之外。但是整个代码也需要在它自己的调度队列中,因为你不能在主队列上阻塞(等待)。
let queue = DispatchQueue(label:"myqueue")
queue.async {
let work = DispatchGroup()
for file in compressedFiles {
work.enter()
DispatchQueue.global(qos: .userInteractive).async {
file.decompress()
work.leave()
}
}
work.notify... // get on main thread here?
}
您的第二个示例将 运行 按顺序排列它们。它正在执行一次调度,运行一个接一个地处理它们。您的第一个示例将 运行 它们并行,将每个分配给不同的工作线程。不幸的是,两者都没有正确使用调度组。
关于调度组,您应该在循环之前定义它,并且在调用 async
之前 enter
定义它。但仅当您从 async
调用中调用异步进程时,才需要手动调用 enter
和 leave
。但考虑到 decompress
可能是一个同步过程,您只需将组提供给 async
,它将为您处理所有事情:
let group = DispatchGroup()
for file in compressedFiles {
DispatchQueue.global(qos: .userInteractive).async(group: group) {
file.decompress()
}
}
group.notify(queue: .main) {
// all done
}
但是与其担心调度组逻辑,并行示例中还有更深层次的问题。具体来说,它会遭受线程爆炸的困扰,它可能会超过 CPU 上的可用内核数。更糟糕的是,如果您有很多文件要解压缩,您甚至可以超过 GCD 在其池中的工作线程数量限制。当发生这种情况时,它可以阻止 运行 在 GCD 上针对该 QoS 进行任何其他操作。相反,您想 运行 它并行,但您希望将它限制在合理的并发度,同时仍然享受并行性,以避免耗尽其他任务的资源。
如果您希望它 运行 并行,但又要避免线程爆炸,通常会达到 concurrentPerform
。这提供了 CPU 支持的最大并行度,但防止了线程爆炸可能导致的问题:
DispatchQueue.global(qos: .userInitiated).async {
DispatchQueue.concurrentPerform(iterations: compressedFiles.count) { index in
compressedFiles[index].decompress()
}
DispatchQueue.main.async {
// all done
}
}
这会将并行度限制为设备内核允许的最大值。它还消除了派遣组的需要。
或者,如果您想享受并行性,但并发度较低(例如,为其他任务留出一些核心,以尽量减少峰值内存使用等),您可以使用操作队列和 maxConcurrentOperationCount
:
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 4 // a max of 4 decompress tasks at a time
let completion = BlockOperation {
// all done
}
for file in compressedFiles {
let operation = BlockOperation {
file.decompress()
}
completion.addDependency(operation)
queue.addOperation(operation)
}
OperationQueue.main.addOperation(completion)
或者马特指出,在 iOS 13(或 macOS 10.15)及更高版本中,您可以:
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 4
for file in compressedFiles {
queue.addOperation {
file.decompress()
}
}
queue.addBarrierBlock {
DispatchQueue.main.async {
// all done
}
}