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

1

8

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.

Eocene answered 21/10, 2013 at 12:12 Comment(3)
Did you consider to use @synchronized instead?Yungyunick
@MartinR, yes, but @synchronized is a classic lock, not based on GCD/queues, and therefor, as I understand it, it is discouraged to use it for reasons of code simplicity and performance. The title of this question is hence misleading, because it contains "lock". What I mean is synchronizing access in a reentrant way with GDC/queues. I simply had no better words than saying "reentrant locking", because that is the name of the solution to the problem that most people know.Eocene
@MartinR, moreover although @synchronized has the advantage of leading to simpler code for reentrant -whatever- (how to call this best?). Still, the locking mechanism behind it has less performance than the mechanism behind GCD queues.Eocene
H
14

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.

Hydrocarbon answered 21/10, 2013 at 13:12 Comment(4)
Thanks for the explanation about the deprecated function and what to use instead, and how. That alone is worth a lot.Eocene
@ipmcc, I am really impressed by your answers and comments to both this and linked topics. Could you please also look at this question I've just posted?Copperhead
@Hydrocarbon Is there a difference in effectiveness or efficiency with this approach in contrast to using a queue with a label & dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)?Helmet
"Label" has an inherent semantic meaning (a human readable string describing or identifying the queue). The "specific" data slot exists specifically to mean <whatever you want it to mean>. If you were going to pick one to use to create a "re-entrant locking system" (<-- derision quotes) specific would be the weapon of choice.Hydrocarbon

© 2022 - 2024 — McMap. All rights reserved.