8

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.

Jeremy Roman
  • 16,137
  • 1
  • 43
  • 44
greg
  • 1,926
  • 17
  • 26
  • Just as a data point, queues and pthread locks are of similar 'weight' (80 bytes vs 64 bytes, comparable acquisition time), but using queues instead of explicit threads saves wired memory for the kernel-side threads (unless you carefully manage your explicit thread lifetimes yourself via some sort of pool) – Catfish_Man Apr 07 '13 at 02:14

1 Answers1

8

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.

Jano
  • 62,815
  • 21
  • 164
  • 192
  • Barriers don't function on global queues. You'd need a private concurrent queue. – Catfish_Man Apr 07 '13 at 02:01
  • Also you can use dispatch_async_f to avoid the Block_copy() – Catfish_Man Apr 07 '13 at 02:04
  • Is it possible to access an ivar from a setter function? I wrote the following broken example: https://gist.github.com/j4n0/a36c0cca0051e7c64e94#file-gistfile1-m-L28 – Jano Apr 07 '13 at 02:26
  • You'd need to pass 'self' in as the context pointer then do self->_something – Catfish_Man Apr 07 '13 at 02:31
  • I ended up passing `(__bridge void*)@[self,something]` as context, but it's a convoluted solution involving array creation and access that doesn't speed up the accessor. I can't think of a simple way to pass self and the parameter. Feel free to edit the answer if you like. – Jano Apr 07 '13 at 02:56
  • Ah, of course. You're quite right, you can't avoid the malloc here. – Catfish_Man Apr 07 '13 at 05:06
  • Thanks for pointing out the barrier option. Are there any convenience libs for generating accessors with this pattern? – greg Apr 07 '13 at 19:44
  • Not that I know of. Use a snippet maybe? I wrote an example to generate them at runtime https://gist.github.com/j4n0/5332644 but the first execution will be slow. Tweaking Clang is over my head. – Jano Apr 07 '13 at 21:42
  • I'll probably wrap something in a macro along with some other custom checking I need in my accessors. Thanks. – greg Apr 09 '13 at 15:47