【问题标题】:Executing asynchronously but writing synchronously异步执行但同步写入
【发布时间】:2014-09-09 01:10:16
【问题描述】:

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

我想使用dispatch_async 将异步块触发到并发队列以加快处理速度,但由于该队列是异步的,我不知道如何协调以将帧串行写入输出。

假设这种情况:帧 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

}

有什么线索吗?

谢谢

【问题讨论】:

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


    【解决方案1】:

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

    来自NSCondition 文档:

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

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

    在您的循环中,您首先声明一个BOOL(最初设置为NO),它指示您的帧是否已被处理,以及一个NSCondition。然后,dispatch_async 到后台队列处理帧和串行队列写入数据。

    当串行队列中的块运行时,锁定NSCondition,然后检查BOOL,看看帧是否已经处理完毕。如果有,请继续写入。如果没有,waitNSCondition 获取signal,并在收到时再次检查。完成后,unlock NSCondition

    当后台队列中的块运行时,锁定NSCondition并处理帧。处理帧时,设置BOOL 表示处理帧。然后是signalunlock NSCondition

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

    // 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 几乎相同,只是条件不同;在这个演员表中,它将是一个int,表示有多少帧正在等待写入。

    在循环之前,定义 readConditionwriteQueueSizemaxWriteQueueSize。在循环内部,首先lockreadCondition,检查是否writeQueueSize >= maxWriteQueueSize。如果不是,请继续读取帧并排队处理和写入。就在您发送到writeQueue 之前,增加writeQueueSize。然后unlockreadCondition

    然后,在分派到writeQueue的块内,一旦写入完成,lockreadCondition,递减writeQueueSizesignalunlockreadCondition

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

    // 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);
    
    NSCondition *readCondition = [[NSCondition alloc] init];
    __block int writeQueueSize = 0;
    const int maxWriteQueueSize = 10;
    
    while (true) { // I'm assuming you have some way to break out of this...
        NSCondition *writeCondition = [[NSCondition alloc] init];
    
        // These need the __block attribute so they can be changed inside the blocks
        __block BOOL frameProcessed = NO;
        __block FrameType outputFrame = nil;
    
        [readCondition lock];
        while (writeQueueSize >= maxWriteQueueSize) {
            [readCondition wait];
        }
    
        // video >> frame;  // read a frame from the video
    
        // dispatch the frame for processing
        dispatch_async(backgroundQueue, ^{
            [writeCondition lock];
    
            processVideo(frame, outputFrame);
            frameProcessed = YES;
    
            [writeCondition signal];
            [writeCondition unlock];
        });
    
        // dispatch the write
        writeQueueSize++; // Increment the write queue size here, before the actual dispatch
        dispatch_async(writeQueue, ^{
            [writeCondition lock];
            while (!frameProcessed) {
                [writeCondition wait]; // this will block the current thread until it gets a signal
            }
    
            writeToVideo(outputFrame);
    
            [writeCondition unlock];
    
            // Decrement the write queue size and signal the readCondition that it changed
            [readCondition lock];
            writeQueueSize--;
            [readCondition signal];
            [readCondition unlock];
        });
    
        [readCondition unlock];
    }
    

    【讨论】:

    • 太棒了!这简直太棒了!!!!!!!!!!!!!!!!!!!不能给你足够的支持!!!!
    • 这种方法只有一个问题,因为与并行执行相比,写入视频很慢,因此会分配数以万计的帧并保留在内存中,直到它们保存到磁盘。内存使用量在 10 秒内从 42 Mb 升级到 2 GB!
    • 我很高兴能帮上忙 :) 我更新了我的答案,用一种方法来限制你的读取,这样你就不会压倒你的写入队列。
    【解决方案2】:

    您可以通过为每个帧提供自己的结果队列并按顺序将所有队列链接在一​​起来做到这一点。我们暂停除第一个之外的所有队列。然后当每一帧结束时,它会恢复下一个结果队列。这将强制队列按照我们想要的顺序传递结果,而不管它们何时完成工作。

    这是一个示例,它仅使用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;
    }
    

    【讨论】:

      猜你喜欢
      • 2012-02-05
      • 1970-01-01
      • 1970-01-01
      • 2019-05-27
      • 2015-09-19
      • 1970-01-01
      • 1970-01-01
      • 2021-07-16
      相关资源
      最近更新 更多