4
votes

I'm using GCD to add thread-safety to a class.

Some public methods of my class are called by other public methods in the class. However, this leads to the re-entrant locking problem: if I protect the appropriate publicly visible methods with synchronous GCD blocks (in some cases), the re-use means sometimes I'll be trying to run anther synchronous block on the current queue, which leads to deadlock.

What's the most elegant solution? An obvious approach is to have internal versions of the appropriate methods, without any GCD blocks, and external public versions of the method that have the GCD blocks wrapping calls to the interal methods. This doesn't quite feel right to me.

2
Your "obvious approach" is perfectly fine, as long as your class doesn't need to make any callbacks out to external code that runs on your queue. In that event, the external code could call another public method on your class and deadlock. - Lily Ballard
There's another, more subtle, issue with it which is that it prevents subclasses from overriding things (since their overrides will not be called, as they aren't the internal version). If that's not a problem, this is the best way to do it. If it is a problem, you need (ugh) recursive locks, and can't use GCD for this. - Catfish_Man

2 Answers

0
votes

Here are a few thoughts:

  1. See if you can't use immutable objects. This means that whenever a method would modify the object it actually returns a new object with the modified state. The blocks would then go on and use this new object. This is simple, requires no special care but is not always possible.

  2. See if your public methods can't use private methods that carry around state. Since each block would carry around its own internal state then you are also thread safe.

If you had some example use case it might lead to a more ideas...

0
votes

I've used a pattern like this very successfully in our C++ classes that use dispatch queues for synchronization:

class Foo
{
public:
    void doSomething() {
        dispatch_sync(fQueue, ^{ doSomething_sync(); });
    }

private:
    void doSomething_sync() { ... }

private:
    dispatch_queue_t fQueue;
};

The general convention here is that for any given _sync method in a class, you only call other _sync methods and not their non _sync public counterpart.