Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement a reentrant locking mechanism in objective-c through GCD?

I have an objective-c class with some methods, which use a GCD queue to ensure that concurrent accesses to a resource take place serially (standard way to do this).

Some of these methods need to call other methods of the same class. So the locking mechanism needs to be re-entrant. Is there a standard way to do this?

At first, I had each of these methods use

dispatch_sync(my_queue, ^{

   // Critical section

});

to synchronize accesses. As you know, when one of these methods calls another such method, a deadlock happens because the dispatch_sync call stops the current executing until that other block is executed, which can't be executed also, because execution on the queue is stopped. To solve this, I then used e.g. this method:

- (void) executeOnQueueSync:(dispatch_queue_t)queue : (void (^)(void))theBlock {
    if (dispatch_get_current_queue() == queue) {
        theBlock();
    } else {
        dispatch_sync(queue, theBlock);
    }
}

And in each of my methods, I use

[self executeOnQueueSync:my_queue : ^{

   // Critical section

}];

I do not like this solution, because for every block with a different return type, I need to write another method. Moreover, this problem looks very common to me and I think there should exist a nicer, standard solution for this.

like image 433
Daniel S. Avatar asked Oct 21 '13 12:10

Daniel S.


1 Answers

First things first: dispatch_get_current_queue() is deprecated. The canonical approach would now be to use dispatch_queue_set_specific. One such example might look like:

typedef dispatch_queue_t dispatch_recursive_queue_t;
static const void * const RecursiveKey = (const void*)&RecursiveKey;

dispatch_recursive_queue_t dispatch_queue_create_recursive_serial(const char * name)
{
    dispatch_queue_t queue = dispatch_queue_create(name, DISPATCH_QUEUE_SERIAL);
    dispatch_queue_set_specific(queue, RecursiveKey, (__bridge void *)(queue), NULL);
    return queue;
}

void dispatch_sync_recursive(dispatch_recursive_queue_t queue, dispatch_block_t block)
{
    if (dispatch_get_specific(RecursiveKey) == (__bridge void *)(queue))
        block();
    else
        dispatch_sync(queue, block);
}

This pattern is quite usable, but it's arguably not bulletproof, because you could create nested recursive queues with dispatch_set_target_queue, and trying to enqueue work on the outer queue from inside the inner one would deadlock, even though you are already "inside the lock" (in derision quotes because it only looks like a lock, it's actually something different: a queue — hence the question, right?) for the outer one. (You could get around that by wrapping calls to dispatch_set_target_queue and maintaining your own out-of-band targeting graph, etc., but that's left as an exercise for the reader.)

You go on to say:

I do not like this solution, because for every block with a different return types, I need to write another method.

The general idea of this "state-protecting serial queue" pattern is that you're protecting private state; why would you "bring your own queue" to this? If it's about multiple objects sharing the state protection, then give them an inherent way to find the queue (i.e., either push it in at init time, or put it somewhere that's mutually accessible to all interested parties). It's not clear how "bringing your own queue" would be useful here.

like image 200
ipmcc Avatar answered Oct 03 '22 23:10

ipmcc