我正在开发一种快速图像扫描应用程序。
在 -captureOutput:didOutputSampleBuffer:fromConnection:
方法中,我选择了 CVPixelBuffer 并将它们添加到 NSOperation
子类中。 NSOperation
获取缓冲区并将其转换为保存在文件系统中的图像。
NSOperation
的-isConcurrent
方法返回 YES。操作创建后,被添加到 NSOperationQueue
。
除了一个影响扫描帧速率的问题外,一切都运行良好。
使用时间分析器,我发现一些 NSOperation 在我创建的 AVCaputureVideoOutput 委托(delegate)的同一线程上运行:
dispatch_queue_t queue = dispatch_queue_create("it.CloudInTouchLabs.avsession", DISPATCH_QUEUE_SERIAL);
[dataOutput setSampleBufferDelegate:(id)self queue:queue];
当一个操作在 AV session 队列的同一个线程上运行时,它会影响帧速率,它只发生在某些线程上,可能 GCD 正在决定在事件线程上调度。
唯一的方法我发现解决这个问题的方法是创建一个单独的线程并将其传递给强制它们在其上运行的单个操作。
还有另一种方法可以强制操作在不同的线程上运行吗?
[编辑]
按照@impcc 的建议,我做了一些测试。
结果非常有趣,即使有点不一致。
测试平台是通过具有 16gb RAM 四核 i7 的 MBP 在 Debug模式下连接的 iPhone 5。该 session 有一个 60fps 的输出,使用 RosyWriter apple 示例代码中的算法进行测试。
针对高优先级队列的 AVSession 队列同步
26 fps,有时队列的线程与一个线程共享 的操作。在委托(delegate)方法中花费的时间有一个 平均0.02s
14 fps,如果主要是为操作创建的线程 没有在这个线程上调用方法,启动时会强制执行 在那个特定的线程上。该线程创建一次并保留 有一个虚拟端口。代表内部花费的时间是 0.008.
AVSession队列并发针对高优先级队列
13.5 fps,其中有时队列的线程与操作之一共享。在委托(delegate)方法中花费的时间有 平均0.02s,与sync基本相同 排队。
14 fps,如果主要是为操作创建的线程 没有在这个线程上调用方法,启动时会强制执行 在那个特定的线程上。该线程创建一次并保留 活着有一个虚拟端口。代表内部花费的时间是 0.008.
结论
并发或串行队列似乎没有太大区别,但并发对我来说并不好,因为我需要采取时间戳来保留单张图片的序列。令我惊讶的事实是使用临时线程的帧下降,即使在委托(delegate)方法中花费的时间要少得多,帧率下降了大约 10fps。只相信 GCD,帧速率更好,但委托(delegate)方法需要超过 2 次才能完成,这可能是因为有时 AV 队列也被 nsoperations 使用。
我真的不明白为什么委托(delegate)内花费的时间似乎与 fps 不相关。不是越快越好吗?
通过进一步调查,似乎确实是在队列中添加和可能执行操作的过程中窃取了时间 si。
最佳答案
我认为您可能误解了isConcurrent
的含义。这是完全可以理解的,因为它的命名非常糟糕。当您从 -isConcurrent
返回 YES
时,它真正的意思是“我将处理与此操作相关的任何并发需求,否则将异步运行。”在这种情况下,NSOperationQueue
可以在您添加操作的线程上同步调用操作的-start
。 NSOperationQueue
期望,因为您已经声明您将管理自己的并发性,所以您的 -start
方法将简单地启动一个异步进程并立即返回。我怀疑这是您问题的根源。
如果您通过覆盖-main
来实现您的操作,那么您几乎肯定希望从isConcurrent
返回NO
。使事情变得更复杂的是,与 isConcurrent
相关的行为多年来发生了变化(但所有这些都包含在官方文档中。)关于如何正确实现的极好的解释a returns-YES
-from-isConcurrent
NSOperation
可以找到here.
我在这里阅读你的问题的方式,听起来你的 NSOperation
子类实际上不需要“管理它自己的并发”,而是你只是想让它异步执行, “在后台线程上”,可能与其他操作“同时”进行。
在确保您的 AVCaptureVideoDataOutputSampleBufferDelegate
回调的最佳性能方面,我建议您将传递给 -setSampleBufferDelegate:queue:
的队列(本身)并发它以高优先级全局并发队列为目标,如下所示:
dispatch_queue_t queue = dispatch_queue_create("it.CloudInTouchLabs.avsession", DISPATCH_QUEUE_CONCURRENT);
dispatch_set_target_queue(queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
[dataOutput setSampleBufferDelegate:(id)self queue:queue];
那么你应该让委托(delegate)回调方法尽可能的轻量——无非就是打包你需要的信息来制作NSOperation
并将它添加到NSOperationQueue
.
这应该确保回调始终优先于 NSOperations
。 (据我了解,NSOperationQueue
的目标是主队列(对于与主线程和运行循环关联的 NSOperationQueue
)或默认优先级后台队列。)这应该允许您的回调以完全跟上帧速率。
另一件重要的事情(另一位评论者提到的)是,如果您使用 GCD 进行所有并发,那么只有 一个 特殊线程 -- 主线程.除此之外,线程只是 GCD 可以(并且将会)彼此互换使用的一种通用资源。线程 ID 为 X 的线程在某一点用于为您的委托(delegate)回调提供服务,而在另一点用于执行您的处理这一事实本身并不表示存在问题。
希望这对您有所帮助!
关于ios - NSOperationQueue,并发操作和线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20738563/