Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recursive synchronization using GCD

I have some model classes that I need to synchronize. There is a main object of type Library that contains several Album objects (imagine a music library, for example). Both the library and albums support serialization by implementing the NSCoding protocol. I need to synchronize modifications to the library with album modifications and the serialization of both classes, so that I know the updates don’t step on each other’s toes and that I don’t serialize objects in the middle of an update.

I thought I would just pass all the objects a shared dispatch queue, dispatch_async all the setter code and dispatch_sync the getters. This is simple and easy, but it does not work, as the program flow is recursive:

// In the Library class
- (void) encodeWithCoder: (NSCoder*) encoder
{
    dispatch_sync(queue, ^{
        [encoder encodeObject:albums forKey:…];
    });
}

// In the Album class, same queue as above
- (void) encodeWithCoder: (NSCoder*) encoder
{
    dispatch_sync(queue, ^{
        [encoder encodeObject:items forKey:…];
    });
}

Now serializing the library triggers the album serialization and since both method use dispatch_sync on the same queue, the code deadlocks. I have seen this pattern somewhere:

- (void) runOnSynchronizationQueue: (dispatch_block_t) block
{
    if (dispatch_get_current_queue() == queue) {
        block();
    } else {
        dispatch_sync(queue, block);
    }
}

Does it make sense, will it work, is it safe? Is there an easier way to do the synchronization?

like image 779
zoul Avatar asked Feb 21 '23 10:02

zoul


1 Answers

For an exposition on recursive locks in GCD, see the Recursive Locks section of the dispatch_async man page. To briefly summarize it, it's generally a good idea to rethink your object hierarchies when something like this occurs.

You can also use dispatch_set_target_queue() to control the hierarchy of execution (targeting subordinate queues at higher level queues) once you've refactored your code such that it's the operations rather than the objects that need to be controlled, but that's also assuming that you weren't able to simply use completion callbacks to accomplish the same synchronization effect (which, frankly, is more recommended since abstract queue hierarchies can be difficult to conceptualize and debug).

I know that's not really the answer you were looking for, but you're kind of in a "you can't get there from here" situation with GCD and a more fundamental rethink of how to do things "the GCD way" is almost certainly necessary in this case.

like image 192
jkh Avatar answered Feb 24 '23 03:02

jkh