I'm debating whether or not to move to a GCD-based pattern for multi-threaded accessors. I've been using custom lock-based synchronization in accessors for years, but I've found some info (Intro to GCD) and there seems to be pros of a GCD-based approach. I'm hoping to start a dialog here to help myself and others weigh the decision.
The pattern looks like:
- (id)something
{
__block id localSomething;
dispatch_sync(queue, ^{
localSomething = [something retain];
});
return [localSomething autorelease];
}
- (void)setSomething:(id)newSomething
{
dispatch_async(queue, ^{
if(newSomething != something)
{
[something release];
something = [newSomething retain];
[self updateSomethingCaches];
}
});
}
On the pro side: you get the benefit of, possibly, non-blocking write access; lower overhead than locks (maybe?); safety from forgetting to unlock before returning from critical code sections; others?
Cons: Exception handling is non-existent so you have to code this into every block in which you might need it.
Is this pattern potentially the recommended method of writing multithreaded accessors?
Are there standard approaches for creating dispatch queues for this purpose? In other words, best practices for trading off granularity? With locks, for example, locking on each attribute is more fine grain than locking on the entire object. With dispatch queues, I could imagine that creation of a single queue for all objects would create performance bottlenecks, so are per-object queues appropriate? Obviously , the answer is highly dependent on the specific application, but are there known performance tradeoffs to help gauge the feasibility of the approach.
Any information / insight would be appreciated.
Is this pattern potentially the recommended method of writing multithreaded accessors?
I guess you wrote that with a serial queue in mind, but there is no reason for it. Consider this:
dispatch_queue_t queue = dispatch_queue_create("com.example", DISPATCH_QUEUE_CONCURRENT);
// same thing as your example
- (NSString*)something {
__block NSString *localSomething;
dispatch_sync(queue, ^{
localSomething = _something;
});
return localSomething;
}
- (void)setSomething:(NSString*)something {
dispatch_barrier_async(queue, ^{
_something = something;
});
}
It reads concurrently but uses a dispatch barrier to disable concurrency while the write is happening. A big advantage of GCD is that allows concurrent reads instead locking the whole object like @property (atomic) does.
Both asyncs (dispatch_async, dispatch_barrier_async) are faster from the client point of view, but slower to execute than a sync because they have to copy the block, and having the block such a small task, the time it takes to copy becomes meaningful. I rather have the client returning fast, so I'm OK with it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With