我会使用串行调度队列和NSCondition 的组合。串行队列确保没有任何写入同时发生,而NSCondition 确保它们以正确的顺序发生。
来自NSCondition 文档:
条件对象在给定的条件下既充当锁又充当检查点
线。锁在测试条件时保护您的代码
执行条件触发的任务。检查点行为
要求在线程继续之前条件为真
它的任务。当条件不成立时,线程阻塞。
在你的具体情况下,我会做这样的事情......
在您的循环中,您首先声明一个BOOL(最初设置为NO),它指示您的帧是否已被处理,以及一个NSCondition。然后,dispatch_async 到后台队列处理帧和串行队列写入数据。
当串行队列中的块运行时,锁定NSCondition,然后检查BOOL,看看帧是否已经处理完毕。如果有,请继续写入。如果没有,wait 从NSCondition 获取signal,并在收到时再次检查。完成后,unlock NSCondition。
当后台队列中的块运行时,锁定NSCondition并处理帧。处理帧时,设置BOOL 表示处理帧。然后是signal 和unlock 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,表示有多少帧正在等待写入。
在循环之前,定义 readCondition、writeQueueSize 和 maxWriteQueueSize。在循环内部,首先lockreadCondition,检查是否writeQueueSize >= maxWriteQueueSize。如果不是,请继续读取帧并排队处理和写入。就在您发送到writeQueue 之前,增加writeQueueSize。然后unlockreadCondition。
然后,在分派到writeQueue的块内,一旦写入完成,lockreadCondition,递减writeQueueSize,signal和unlockreadCondition。
这应该确保在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];
}