Implementing concurrent read exclusive write model with GCD
I am trying to understand the proper way of using Grand Central Dispatch (GCD) to implement concurrent read exclusive write model of controlling access to a resource.
Suppose there is a NSMutableDictionary that is read a lot and once in awhile updated. What is the proper way of ensuring that reads always work with consistent state of the dictionary? Sure I can use a queue and serialize all read and write access to the dictionary, but that would unnecessarily serialize reads which should be allowed to access the dictionary concurrently. At first the use of groups here sounds promising. I could create a 'read' group and add every read operation to it. That would allow reads to happen at the same time. And then when the time comes to do an update, I could dispatch_notify() or dispatch_wait() as part of a write operation to make sure that all reads complete before the update is allowed to go on. But then how do I make sure that a subsequent read operation does not start until the write operation completes?
Here's an example with the dictionary I mentioned above:
R1: at 0 seconds, a read comes in which needs 5 seconds to complete R2: at开发者_StackOverflow 2 seconds another read comes in which needs 5 seconds to complete W1: at 4 seconds a write operation comes needing access to dictionary for 3 sec R3: at 6 seconds another read comes in which needs 5 seconds to complete W2: at 8 seconds another write operation comes in also needing 3 seconds to completeIdeally the above should play out like this:
R1 starts at 0 seconds, ends at 5 R2 starts at 2 seconds, ends at 7 W1 starts at 7 seconds, ends at 10 R3 starts at 10 seconds, ends at 15 W2 starts at 15 seconds, ends at 18Note: even though R3 came at 6 seconds, it was not allowed to start before W1 because W1 came earlier.
What is the best way to implement the above with GCD?
You've got the right idea, I think. Conceptually, what you want is a private concurrent queue that you can submit "barrier" blocks to, such that the barrier block waits until all previously submitted blocks have finished executing, and then executes all by itself.
GCD doesn't (yet?) provide this functionality out-of-the-box, but you could simulate it by wrapping your read/write requests in some additional logic and funnelling these requests through an intermediary serial queue.
When a read request reaches the front of the serial queue, dispatch_group_async
the actual work onto a global concurrent queue. In the case of a write request, you should dispatch_suspend
the serial queue, and call dispatch_group_notify
to submit the work onto the concurrent queue only after the previous requests have finished executing. After this write request has executed, resume the queue again.
Something like the following could get you started (I haven't tested this):
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);
});
});
}
Use dispatch_async to push these wrapped blocks onto a serial queue.
精彩评论