swift - Swift DispatchQueue concurrentPerform OpenGL 并行渲染
问题描述
我有一个用于 Linux 的 c++ 中的无头 EGL 渲染器,我用绑定包装了它以在 Swift 中使用。它工作得很好——我可以并行渲染创建多个上下文并在单独的线程中渲染,但我遇到了一个奇怪的问题。首先,我已经包装了特定于渲染器的所有 GL 调用,它的上下文在它自己的串行队列中,如下所示。
func draw(data:Any) -> results {
serial.sync {
//All rendering code for this renderer is wrapped in a unique serial queue.
bindGLContext()
draw()
}
}
为了在渲染器之间批处理数据,我使用了 DispatchQueue.concurrentPerform。它工作正常,但是当我尝试使用 DispatchGroup 创建并发队列时,会发生一些奇怪的事情。即使我已经将所有 GL 调用包装在串行队列中,GL 上下文也会变得混乱,并且所有 gl 调用都无法分配纹理/缓冲区/等。
因此,我试图了解这两者之间的区别以及为什么一个有效而另一个无效。任何想法都会很棒!
//This works
DispatchQueue.concurrentPerform(iterations: renderers.count) { j in
let batch = batches[j]
let renderer = renderers[j]
let _ = renderer.draw(data:batch)
}
//This fails – specifically GL calls fail
let group = DispatchGroup()
let q = DispatchQueue(label: "queue.concurrent", attributes: .concurrent)
for (j, renderer) in renderers.enumerated() {
q.async(group: group) {
let batch = batches[j]
let _ = renderer.draw(data:batch)
}
}
group.wait()
解决方案
编辑:
我会确保 OpenGL 包装器实际上是线程安全的。如果多个渲染器同时进行 OpenGL 调用,则每个渲染器都有自己的串行队列可能无济于事。该DispatchQueue.concurrentPerform
版本可能有效,因为它只是串行运行。
原答案:
我怀疑 OpenGL 的失败与内存限制有关。当您将许多任务分派到并发队列时,GCD 不会做任何聪明的事情来限制启动的任务数量。如果一堆正在运行的任务在执行 IO 时被阻塞,它可能会在其中任何一个完成之前启动越来越多的任务,从而吞噬越来越多的内存。这是 Mike Ash 关于这个问题的详细文章。
我猜这是可行的,DispatchQueue.concurrentPerform
因为它在内部具有某种额外的逻辑以避免产生过多的线程,尽管它没有很好的文档记录,并且这里可能会发生特定于平台的事情。如果它所做的只是分派到并发队列,我不确定为什么该函数甚至会存在。
如果您想将大量项目直接分派到 a DispatchQueue
,特别是如果这些项目有一些非 CPU 绑定的组件,您需要自己添加一些额外的逻辑来限制开始的任务数量。这是Soroush Khanlou 的 GCD 手册中的一个示例:
class LimitedWorker {
private let serialQueue = DispatchQueue(label: "com.khanlou.serial.queue")
private let concurrentQueue = DispatchQueue(label: "com.khanlou.concurrent.queue", attributes: .concurrent)
private let semaphore: DispatchSemaphore
init(limit: Int) {
semaphore = DispatchSemaphore(value: limit)
}
func enqueue(task: @escaping () -> ()) {
serialQueue.async(execute: {
self.semaphore.wait()
self.concurrentQueue.async(execute: {
task()
self.semaphore.signal()
})
})
}
}
它使用信号量来限制并发队列上正在执行的并发任务的数量,并使用串行队列将新任务提供给并发队列。self.semaphore.wait()
如果已在并发队列上安排了最大数量的任务,则新入队的任务会阻塞。
你会像这样使用它:
let group = DispatchGroup()
let q = LimitedWorker(limit: 10) // Experiment with this number
for (j, renderer) in renderers.enumerated() {
group.enter()
q.enqueue {
let batch = batches[j]
let _ = renderer.draw(data:batch)
group.leave()
}
}
group.wait()
推荐阅读
- firebase - Firebase 动态链接替换了一些字符。是否有哪些字符正在更改的列表?
- git - 为什么我无法在预推中读取标准输入?
- r - {purrr}:如何在用完函数的所有参数时用 purrr::map() 替换一个简单的循环?
- xcode - 重命名后,XCode 项目不会在 mac 启动时自动加载
- spring-boot - 如何将待办事项中的属性标签的资源值放入application.properties
- android - Android SharedPreferences 漏洞
- linux - 如何确定在对配方进行位烘焙时生成哪个二进制文件
- linux - 无法在 bash 脚本中声明数组
- excel - 如何根据所述分隔符之间的第一个字母对 Excel 中两个分隔符之间的值求和?
- java - 如何转换json?