Swift DispatchQueue:串行或并行

Ali*_*eeb 2 concurrency multithreading grand-central-dispatch swift

在我的应用程序中,我必须在后台同时解压缩多个文件。哪个代码会在多个线程上并行执行compressedFiles数组:

for file in compressedFiles {
  DispatchQueue.global(qos: .userInteractive).async {
    let work = DispatchGroup()
    work.enter()
    file.decompress()
    work.leave()
  }
}
Run Code Online (Sandbox Code Playgroud)

或者:

DispatchQueue.global(qos: .userInteractive).async {
  for file in compressedFiles {
    let work = DispatchGroup()
    work.enter()
    file.decompress()
    work.leave()
  }
}
Run Code Online (Sandbox Code Playgroud)

另外,如果我想在其中一个文件解压过程完成后收到通知,如何利用 DispatchGroup 类?wait()和notify()放在哪里?

谢谢。

Rob*_*Rob 6

您的第二个示例将按顺序运行它们。它正在执行一次调度,一个接一个地运行它们。您的第一个示例将并行运行它们,将每个线程分派到不同的工作线程。但不幸的是,两者都没有正确使用调度组。

关于调度组,您应该在循环之前和enter调用之前定义它async。但仅当您从调用中调用异步进程时才需要手动调用enter和。但考虑到这可能是一个同步过程,您只需将组提供给,它就会为您处理一切:leaveasyncdecompressasync

let group = DispatchGroup()

for file in compressedFiles {
    DispatchQueue.global(qos: .userInteractive).async(group: group) {
        file.decompress()
    }
}

group.notify(queue: .main) {
    // all done
}
Run Code Online (Sandbox Code Playgroud)

但与其担心调度组逻辑,并行示例中还存在更深层次的问题。具体来说,它会遭受线程爆炸的影响,可能会超过 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
    }
}
Run Code Online (Sandbox Code Playgroud)

这会将并行度限制为设备上内核允许的最大程度。它还消除了对调度组的需要。


或者,如果您想享受并行性,但并发程度较低(例如,让一些核心可用于其他任务,最大限度地减少峰值内存使用等),您可以使用使用操作队列和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)
Run Code Online (Sandbox Code Playgroud)

或者 Matt 指出,在 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
    }
}
Run Code Online (Sandbox Code Playgroud)