3
@interface ViewController ()
@property (nonatomic, strong) NSString *someString;
@end

@implementation ViewController

@synthesize someString = _someString;

- (NSString *)someString {
    __block NSString *tmp;
    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        tmp = _someString;
    });
    return tmp;
}

- (void)setSomeString:(NSString *)someString {
    __block NSString *tmp;
    dispatch_barrier_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        tmp = someString;
    });
    _someString = tmp;
}
@end

some said it's better than @synchronized way because all the locking is handled down in GCD.

dopcn
  • 4,218
  • 3
  • 23
  • 32
  • Why do you want to lock these getters and setters? – Rob Sanders Mar 17 '15 at 10:00
  • this is thread safety way I learn from a book. But I'm not sure about it – dopcn Mar 17 '15 at 13:39
  • I'm not pro enough to give you the difference between `@synchronized` and `dispatch_sync` as far as I'm aware they both act as [mutex locks](http://stackoverflow.com/questions/34524/what-is-a-mutex), ensuring that your code is thread safe. I am also aware that `@synchronized` is quite performance expensive ([check this out](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html)). Personally I wouldn't worry too much about using `@synchronized` unless you need to boost performance, or a lack of it would be detrimental to your app. – Rob Sanders Mar 17 '15 at 13:57
  • The setter here makes no sense. The GCD queue should be protecting the assignment of `_someString` not `tmp`. – ipmcc Mar 17 '15 at 19:16

2 Answers2

8

First off, your setter makes no sense at all, and using the default concurrent queue is also probably not what you want. Your code should probably look more like:

@interface ViewController ()
@property (nonatomic, copy) NSString *someString;
@end

@implementation ViewController
{
    dispatch_queue_t _stateGuardQueue;
}

- (instancetype)init
{
    if (self = [super init])
    {
        _stateGuardQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
    }
    return self;
}

@synthesize someString = _someString;

- (NSString *)someString {
    __block NSString *tmp;
    dispatch_sync(_stateGuardQueue, ^{
        tmp = _someString;
    });
    return tmp;
}

- (void)setSomeString:(NSString *)someString {
    NSString* tmp = [someString copy];
    dispatch_barrier_async(_stateGuardQueue, ^{
        _someString = tmp;
    });
}

@end

The changes I made:

  • Make the setter actually do the mutation inside the critical section
  • Use a private, per-instance concurrent queue instead of the global default concurrent queue; Submitting barrier blocks to default concurrent queues doesn't do what you think it does. (See the docs)
  • Change dispatch_barrier_sync to dispatch_barrier_async there's no point at all in waiting synchronously for the setter block to return, as there's no way to get a stale read on the current thread.
  • Change the property to have copy semantics, which is always good practice with value-semantic types (NSString, etc.) This is especially important in cases where the property might be read concurrently from multiple threads.

The thing to know is that, in isolation, this pattern provides no more "safety" than atomic properties, so you should arguably just use those (less code, etc). As to the performance question, yes, for this particular use, GCD will certainly out-perform @synchronized. For one, it allows concurrent reads, where @synchronized will serialize concurrent reads. Without testing, I would expect atomic properties to out-perform both. That said atomic properties, and protecting single operations in this way in general, are rarely an adequate concurrency strategy.

ipmcc
  • 29,581
  • 5
  • 84
  • 147
  • your setter makes no sense too, because serial queue always monopolistically – daleijn Apr 13 '16 at 16:21
  • 2
    What are you talking about? The queue in my example is not serial, it's concurrent. – ipmcc Apr 13 '16 at 18:45
  • Why do you copy the parameter before the barrier block inside the setter? – valeCocoa Jun 03 '17 at 15:35
  • @valeCocoa As mentioned, copying value-semantic types in setters is a standard best practice. The `copy` isn't particularly important w/r/t to the concurrency question. The `copy` operation is done outside the barrier block because you want to limit the amount of time spent in the barrier block, and there's no reason to do it *inside* the block. – ipmcc Jun 04 '17 at 13:50
  • @ipmcc I see, but is it really still necessary under ARC to copy the parameter value? I've been using this same approach (barrier async blocks on a private concurrent queue) for my thread safe properties setters, and so far I didn't run in any problem without doing that para,eter copy before entering the block. Looking at your code a doubt about of all those setters of mine has arisen… – valeCocoa Jun 04 '17 at 21:49
  • @valeCocoa Yup, still necessary under ARC. The canonical example is this: if you have a `NSString`-typed property, I can pass you an `NSMutableString` (because it's a subclass of `NSString`). If you don't `copy` it, but instead simply `retain` it, you can't be assured exclusive ownership of it, and so some other code (potentially on some other thread) could mutate that `NSMutableString` after the fact, and in a way that violates your threading rules. On immutable, value-semantic types, `-copy` is implemented as `-retain`, so copies of immutable types are free w/r/t memory. – ipmcc Jun 05 '17 at 11:19
  • @ipmcc: of course it's so for mutable objects or possible mutable objects from from class clusters. Thanks for the insight! I guess I didn't have such a problem because I was dealing with immutable objects in those setters. Thanks again. – valeCocoa Jun 07 '17 at 19:19
  • In Objective-C any class can become a class cluster via subclassing. :) The real thing is "value semantics" vs. "reference semantics" and unfortunately it's not formalized (although adopting `NSCopying` is a pretty good hint). – ipmcc Jun 07 '17 at 22:55
0

why not like below ,using async method:

- (NSString *)someString {
    __block NSString *tmp;
    dispatch_async(_stateGuardQueue, ^{
        tmp = _someString;
    });
    return tmp;
}
SHR
  • 7,940
  • 9
  • 38
  • 57
CrusherW
  • 1
  • 2
  • Because he is using a concurrent queue and is allowing concurrent reads. By using dispatch_barrier_async he ensures that the write is safe, as it blocks any (concurrent) reads. He could have used dispatch_async if he used a serial queue. – jvarela Oct 02 '18 at 00:53