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 调用中调用异步进程时,才需要手动调用 enterleave。但考虑到 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
    }
}