从单个串行队列访问Swift实例变量会导致Thread Sanitizer问题

时间:2019-07-19 10:35:51

标签: swift multithreading metal

我有一个MTLBuffer实例变量,我只能从与NSManagedObjectContext关联的串行队列中访问(读取)。

我使用MTLParallelRenderCommandEncoder将绘图工作分为来自主线程的部分和发生在NSManagedObjectContext的串行队列中的部分。

访问此MTLBuffer时,仍然遇到线程清理问题(或在Metal中崩溃)。为什么会这样?

我的代码基本上像这样:

final class Renderer: NSObject {
    private var moc: NSManagedObjectContext
    private var geometryBuffer:MTLBuffer!

    init() {
        geometryBuffer=device.makeBuffer(length: 1024*1024, options: .storageModeShared)
    }

    func draw(with renderEncoder: MTLRenderCommandEncoder) {
        moc.perform {
            renderEncoder.setVertexBuffer(self.geometryBuffer, offset: 0, index: 0) //CRASH HERE (1)
            renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: self.geometryBuffer.length/(MemoryLayout<Float>.size*3)) //OR CRASH HERE (2)
        }
    }
}

class ViewController: MTKViewDelegate {
    func draw(in view: MTKView) {
        guard let parallelRenderCommandEncoder = commandBuffer.makeParallelRenderCommandEncoder(descriptor: renderPassDescriptor) else { return }
        guard let layersRenderEncoder=parallelRenderCommandEncoder.makeRenderCommandEncoder() else { return }
        guard let renderEncoder = parallelRenderCommandEncoder.makeRenderCommandEncoder() else { return }

        renderer.draw(layersRenderEncoder) //This will dispatch to serial queue

        //Continue with the other renderEncoder
    }
}

启用线程清除程序后,问题将通过以下堆栈跟踪捕获到(1):

#0  0x00007fff664f42de in __cxa_throw ()
#1  0x00007fff664e62e5 in std::__1::__throw_system_error(int, char const*) ()
#2  0x00007fff664a0acd in std::__1::mutex::lock() ()
#3  0x00007fff578cfbc2 in -[MTLToolsCommandBuffer addRetainedObject:] ()
#4  0x00007fff57930431 in -[MTLDebugRenderCommandEncoder setVertexBuffer:offset:atIndex:] ()

没有线程清理器,我在(2)上得到了EXC_BAD_ACCESS (code=EXC_I386_GPFLT)

除了“我的”串行队列之外,还可以访问MTLBuffer吗?

1 个答案:

答案 0 :(得分:0)

MTLParallelRenderCommandEncoder要求其endEncoding调用在所有子编码器完成之后进行。

这可以通过使用DispatchGroup完成,如以下更新的代码所示(还添加了{endEncoding命令)

final class Renderer: NSObject {
    private var moc: NSManagedObjectContext
    private var geometryBuffer:MTLBuffer!

    init() {
        geometryBuffer=device.makeBuffer(length: 1024*1024, options: .storageModeShared)
    }

    func draw(with renderEncoder: MTLRenderCommandEncoder, group: DispatchGroup) {
        group.enter()

        moc.perform {
            renderEncoder.setVertexBuffer(self.geometryBuffer, offset: 0, index: 0) //CRASH HERE (1)
            renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: self.geometryBuffer.length/(MemoryLayout<Float>.size*3)) //OR CRASH HERE (2)

        renderEncoder.endEncoding
        group.leave()
        }
    }
}

class ViewController: MTKViewDelegate {
    var encodingGroup=DispatchGroup()

    func draw(in view: MTKView) {
        guard let parallelRenderCommandEncoder = commandBuffer.makeParallelRenderCommandEncoder(descriptor: renderPassDescriptor) else { return }
        guard let layersRenderEncoder=parallelRenderCommandEncoder.makeRenderCommandEncoder() else { return }
        guard let renderEncoder = parallelRenderCommandEncoder.makeRenderCommandEncoder() else { return }

        renderer.draw(layersRenderEncoder, group: encodingGroup) //This will dispatch to serial queue

        //Continue with the other renderEncoder
        /* Do some encoding work here */

        //Finish this encoder
        renderEncoder.endEncoding()

        //Wait for all encoding threads to finish
        encodingGroup.wait()

        //End final encoder
        parallelRenderCommandEncoder.endEncoding()
    }
}