异步执行但同步写入

时间:2014-09-09 01:10:16

标签: ios cocoa concurrency queue grand-central-dispatch

我遇到这种情况:视频必须逐帧处理,但正在处理帧时,输出必须按顺序写入文件。

我想使用dispatch_async将异步块发送到并发队列来加速进程,但由于这个队列是异步的,所以我不知道如何协调将帧连续写入输出。 / p>

假设这种情况:帧1,2,3,4和5被发送到并发队列进行处理。因为任何块都可以随时完成,所以第4帧可能是第一个完成,然后是5,3,1,2。那么我将如何设法按顺序将帧写入输出?

我有这样的代码:

dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

while (true) {

    video >> frame;  // read a frame from the video

    dispatch_async(aQueue, ^{
         processVideo(frame, outputFrame);
         writeToVideo(outputFrame); // this is here just to show what needs to be done
    });

    // bla bla

}

任何线索?

感谢

2 个答案:

答案 0 :(得分:4)

我会使用串行调度队列和NSCondition的组合。串行队列确保没有任何写入同时发生,而NSCondition确保它们以正确的顺序发生。

来自NSCondition文档:

  

条件对象充当给定的锁定和检查点   线。锁可以在测试条件时保护您的代码   执行由条件触发的任务。检查点行为   要求在线程继续之前条件为真   它的任务。虽然条件不成立,但线程会阻塞。

在您的具体情况下,我会做这样的事情......

在循环中,您首先声明BOOL(最初设置为NO),表示您的框架是否已被处理,以及NSCondition。然后,dispatch_async同时为后台队列处理帧,并将串行队列写入数据。

当串行队列中的块运行时,锁定NSCondition,然后检查BOOL以查看帧是否已被处理。如果有,继续写。如果没有,wait来自signal的{​​{1}}并在收到时再次检查。完成后,NSCondition unlock

当后台队列中的块运行时,锁定NSCondition并处理帧。处理帧时,设置NSCondition以指示帧已处理。然后是BOOLsignal unlock

注意:重要的是,您只能访问表示已处理框架的NSCondition以及BOOL内的outputFrame锁;锁定确保它们在线程之间保持同步。

NSCondition

注意:在上面的代码中,// Create the background and serial queues dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_queue_t writeQueue = dispatch_queue_create("writeQueue", DISPATCH_QUEUE_SERIAL); while (true) { // I'm assuming you have some way to break out of this... NSCondition *condition = [[NSCondition alloc] init]; // These need the __block attribute so they can be changed inside the blocks __block BOOL frameProcessed = NO; __block FrameType outputFrame = nil; // video >> frame; // read a frame from the video // dispatch the frame for processing dispatch_async(backgroundQueue, ^{ [condition lock]; processVideo(frame, outputFrame); frameProcessed = YES; [condition signal]; [condition unlock]; }); // dispatch the write dispatch_async(writeQueue, ^{ [condition lock]; while (!frameProcessed) { [condition wait]; // this will block the current thread until it gets a signal } writeToVideo(outputFrame); [condition unlock]; }); } 还有一个半细微的技巧。由于它是在循环内部而不是在外部声明的,因此每个块将捕获与其帧相关联的块。


更新:添加BOOL frameProcessed以便阅读。

  

因为与并行执行相比,写入视频的速度很慢,   数以万计的帧被分配并存放在内存中,直到它们为止   保存到磁盘。

我会处理这个问题是通过使用另一个NSCondition限制读取,如果等待在NSCondition中写入太多帧,则会阻止读取。这个概念几乎与我们之前添加的writeQueue相同,只是一个不同的条件;在这个演员表中,它将是NSCondition,表示等待写入的帧数。

在循环之前,请定义intreadConditionwriteQueueSize。在循环内部,首先maxWriteQueueSize lock,检查是否readCondition。如果不是,继续阅读框架并排队处理和写入。在您发送到writeQueueSize >= maxWriteQueueSize之前,请增加writeQueue。然后writeQueueSize unlock

然后,在发送到readCondition的块内,写入完成后,writeQueue lock,递减readConditionwriteQueueSize和{{1 }} signal

这应该确保在unlock中等待的readCondition块永远不会超过maxWriteQueueSize。如果有多个块等待,它将有效地暂停从视频中读取帧,直到writeQueue准备好更多。

writeQueue

答案 1 :(得分:0)

您可以通过为每个帧提供自己的结果队列,并按顺序将所有队列链接在一​​起来完成此操作。我们暂停除第一个队列之外的所有队列。然后,当每个帧完成时,它将恢复下一个结果队列。这将迫使队列按照我们想要的顺序传递结果,无论他们何时完成工作。

这是一个仅使用sleep来模拟一些随机工作量并以正确顺序打印结果的示例。这里使用dispatch_group来防止程序过早退出。在你的情况下你可能不需要它。

int main(int argc, const char * argv[])
{
  @autoreleasepool {
    dispatch_queue_t mainQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();

    dispatch_queue_t myQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);

    for (unsigned x = 1; x <= 5; x++ ) {

      // Chain the queues together in order; suspend all but the first.
      dispatch_queue_t subQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
      dispatch_set_target_queue(subQueue, myQueue);
      dispatch_suspend(subQueue);

      dispatch_group_async(group, mainQueue,^{

        // Perform a random amount of work
        u_int32_t sleepTime = arc4random_uniform(10);
        NSLog(@"Sleeping for thread %d (%d)", x, sleepTime);
        sleep(sleepTime);

        // OK, done with our work, queue our printing, and tell the next guy he can print
        dispatch_sync(myQueue, ^{
          printf("%d ", x);
          dispatch_resume(subQueue);
        });
      });

      myQueue = subQueue;
    }

    // Wait for the whole group to finish before terminating
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
  }

  return 0;
}