0

Suppose if I want to implement both getter and setter I will do like this -

@interface Person () {
 dispatch_queue_t _myConcurrentQueue;
}

@property (nonatomic, copy) NSString *personName;

@end

@implementation Person


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

@synthesize personName = _personName;

- (NSString *)personName {
    __block NSString *tmp;
    dispatch_sync(_myConcurrentQueue, ^{
        tmp = _personName;
    });
    return tmp;
}

- (void)setpersonName:(NSString *)personName {
    NSString* tmp = [personName copy];
    dispatch_barrier_async(_myConcurrentQueue, ^{
        _personName = tmp;
    });
}

@end

But if I want to initialize my property lazily, then how can I make it thread safe? Example -

- (NSString *)personName {
        If (!_personName) {
           _personName = "Some Name"
         }

    return _personName;
}

What should I use serial queue with dispatch_async or concurrent queue with dispatch_async barrier and why?

srus2017
  • 394
  • 2
  • 14
  • 1
    This is really annoyingly complicated to build (and tricky to make sure it's correct), and introduces overhead in every get. Do you *really* need this? For it to be worthwhile, the property needs to be extremely expensive to create, and very likely to *never* be accessed. (If you do access it, laziness introduced a performance cost that you probably won't get back.) There are some cases where it makes sense, but unless your work in Instruments has pinpointed this an important optimization, I would avoid introducing laziness this way. Laziness makes more sense for globals than instance vars. – Rob Napier Jun 28 '18 at 21:58
  • (It also happens to be very easy to implement for globals…) – Rob Napier Jun 28 '18 at 22:00
  • Perfect! Thanks for your comment. I will analyse my case and refactor my code if needed. – srus2017 Jun 28 '18 at 22:57

1 Answers1

2

Well, in your trivial case using a string literal (although you neglected the @), there's little chance to go wrong.

However, for the general case where you initialize the instance variable in a more dynamic fashion with possible side effects, no, it's not thread-safe. Two threads can both be in the getter simultaneously. They can each submit a task to the queue. Since the queue is concurrent and neither task was submitted as a barrier, both tasks can run simultaneously. They might both test !_personName and both determine that they need to initialize it. They will do redundant work and, if it has side effects, that could screw things up.

It's also possible that the compiler and/or CPU can re-order how things are done. One thread may think that _personName is non-nil and return the pointer, even though the other thread is still working on initializing the object. That is, the assignment to _personName may happen before all of the effects of the expression on the right-hand side of the assignment have completed and are visible to the other thread.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • means I should use dispatch_async_barrier for this? – srus2017 Jun 26 '18 at 22:59
  • Well, that would work, but then you're just reinventing a serial queue. Might as well just use a serial queue to begin with. You might also consider not bothering to make the property thread-safe as such and instead ensure thread safety at a higher level, such as requiring clients of your `ViewController` class to only access it on the main thread. Or something like that. – Ken Thomases Jun 26 '18 at 23:54
  • What if I have multiple property in my model for which I want to write thread safe getter. Should I use serial queue in this case? If all property is initializing lazily. – srus2017 Jun 27 '18 at 04:00