使用GCD实现并发读独占写模型

3
我正在尝试了解使用Grand Central Dispatch (GCD)实现并发读排他写模式以控制资源访问的正确方法。
假设有一个NSMutableDictionary经常被读取,偶尔会被更新。如何确保读取始终使用字典的一致状态呢?当然我可以使用一个队列来序列化所有对字典的读和写访问,但这将不必要地序列化读取,因为读取应该可以并发访问字典。使用组的方法听起来很有前途。我可以创建一个“读”组并将每个读操作添加到其中。这样就允许读取同时进行。然后,在进行更新时,我可以将dispatch_notify()或dispatch_wait()作为写操作的一部分进行调度,以确保在允许更新之前所有读取都完成。但是,如何确保后续的读操作在写操作完成之前不开始?
以下是我提到的字典的示例: R1:在0秒时读取,需要5秒才能完成 R2:在2秒时另一个读取操作进入,需要5秒才能完成 W1:在4秒时进行写操作,需要3秒钟才能访问字典 R3:在6秒时另一个读取操作进入,需要5秒才能完成 W2:在8秒时进行另一个写操作,也需要3秒钟才能完成
理想情况下,上述过程应该如下所示: R1从0秒开始,结束于5秒 R2从2秒开始,结束于7秒 W1从7秒开始,结束于10秒 R3从10秒开始,结束于15秒 W2从15秒开始,结束于18秒
请注意:即使R3在6秒钟时到达,但它也不被允许在W1之前开始,因为W1先到。
使用GCD实现上述内容的最佳方法是什么?
1个回答

3
您的想法是正确的。从概念上讲,您需要一个私有并发队列,可以向其中提交“barrier”块,使得该屏障块等待所有先前提交的块执行完毕后,再独自执行。
GCD目前似乎没有提供这种功能,但您可以通过将读/写请求包装在一些额外的逻辑中,并通过中介串行队列传递这些请求来模拟它。
当读请求到达串行队列的前面时,请使用dispatch_group_async将实际工作分派到全局并发队列中。对于写请求,您应该dispatch_suspend串行队列,并调用dispatch_group_notify,仅在先前的请求完成执行后才将工作提交到并发队列中。此写请求执行完毕后,再次恢复队列。
以下代码片段可能会帮助您入门(我还没有测试过):
dispatch_block_t CreateBlock(dispatch_block_t block, dispatch_group_t group, dispatch_queue_t concurrentQueue) {
    return Block_copy(^{ 
        dispatch_group_async(concurrentQueue, group, block);
    });
}

dispatch_block_t CreateBarrierBlock(dispatch_block_t barrierBlock, dispatch_group_t group, dispatch_queue_t concurrentQueue) {
    return Block_copy(^{
        dispatch_queue_t serialQueue = dispatch_get_current_queue();
        dispatch_suspend(serialQueue);
        dispatch_group_notify(group, concurrentQueue, ^{
            barrierBlock();
            dispatch_resume(serialQueue);
        });
    });
}

使用dispatch_async将这些封装块推送到串行队列中。

Nick,感谢你的答复!我还没有测试过您的解决方案,但它似乎会起作用!(唯一需要更正的是我们需要Block_copy传递给CreateBlock和CreateBarrierBlock的块,因为这些块不是同步执行的) - Yurie
2
自iOS 4.3 / OS X 10.7起,Apple现在提供了屏障块API。您需要使用属性DISPATCH_QUEUE_CONCURRENT创建队列,并且每当您想要向队列添加屏障块时,请使用dispatch_barrier_async()或dispatch_barrier_sync()。文档位于<dispatch/queue.h>中。 - Christopher Rogers
@Christopher 很好的发现!我没有意识到苹果已经公开了那个。 - Nick Hutchinson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接