首页 > 解决方案 > 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()

标签: swiftlinuxopengl

解决方案


编辑:

我会确保 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()

推荐阅读