Boon
Boon

Reputation: 41470

synchronized block within dispatch_async

I have seen code that dispatch async to a main queue or private dispatch queue (serial) and then in the dispatch code block is @synchronized. Under what circumstance do you want to do that? Isn't a serial queue already providing the synchronization needed?

Can the synchronized block be replaced with another GCD dispatch?

Upvotes: 1

Views: 2170

Answers (3)

Jano
Jano

Reputation: 63667

Thread-safe is about making mutable shared state either immutable, or unshared. In this case, synchronize and serial queues are ways to temporarily unshare (prevent concurrent access), and both are valid.

However, consider the case where you have disjoint sets of related info inside the same object. For example, a bill with 1) parts of an address (city, street, etc), and 2) price, taxes, discount. Both need to be protected to avoid inconsistent state (object A sets a new street, while object B reads the old city and the new street), but both are unrelated. In this case, you should use the lower level of granularity to avoid blocks between unrelated code.

So a rule would be: don't use synchronize on unrelated sets of variables inside the same object, because it would cause unneeded blocks between them. You can use a queue + synchronize, or a queue per set, but not synchronized on both sets.

The key is that synchronize refers to the one and only intrinsic lock of the object, and that token can only be held by once. It accomplishes the same goal as if you routed all related code through one queue, except that you can have multiple queues (and thus, lower granularity), but only one intrinsic lock.

Back to your example. Assuming that the object is documented as “state X is protected by synchronize”, the use of synchronize inside the queue is useful to block related methods that could access that same state. So maybe queue and synchronized are protecting different things, or the serial queue is there to perform a different task.

Another reason to prefer a queue is to write a more sophisticated pattern like a read-write lock. Example:

NSMutableDictionary *_dic = [NSMutableDictionary new];
dispatch_queue_t _queue = dispatch_queue_create("com.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);

- (id) objectForKey:(id)key
{
    __block obj;
    dispatch_sync(_queue, ^{
        obj = [_dic objectForKey: key];
    });
    return obj;
}

- (void) setObject:(id)obj forKey:(id)key
{
    // exclusive access while writing
    dispatch_barrier_async(_queue, ^{
        [_dic setObject:obj forKey:key];
    });
}

Upvotes: 1

Andrew Madsen
Andrew Madsen

Reputation: 21373

@synchronized() ensures that contained code (for a given token as the argument to @synchronized) is only run on one thread at a time.

Blocks submitted to a serial queue are executed one at a time, ie. a given block is not executed until all blocks submitted before it have finished executing. As long as a shared resource is only being accessed from code running on a serial queue, there's no need to synchronize/lock access to it. However, just because a given queue is serial, doesn't mean that other queues/threads (even serial queues!) aren't running simultaneously, and accessing the same shared resource.

Using @synchronized() is one way to prevent these multiple threads/queues from accessing the resource at the same time. Note that all code that access the shared resource needs to be wrapped with @synchronized().

Yes, you can use another GCD dispatch instead of a synchronized block. The "GCD way" of doing this would be to serialize all access to the shared resource using a serial queue. So, any time access to the shared resource needs to be made, you dispatch that code (using either dispatch_sync() or dispatch_async() depending on use case) to the serial queue associated with the resource. Of course, this means that the resource-access-queue must be visible/accessible to all parts of the program that access the shared resource. (You essentially have the same problem with @synchronized() in that its lock token must be accessible wherever it needs to be used, but it's a little easier since it can just be a string constant.)

Upvotes: 3

Leonardo
Leonardo

Reputation: 9857

The queue yes, it is synchronized, but if you access any 'external' object within, they are not synchronized.

If there are many threads, you know that each one will have its turn on the object. A tipical case is when you perform a CoreData import asynchronously, you have to @synchronized the context or the store coordinator.

Upvotes: 1

Related Questions