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