-->

Thoughts in accessing read only objects from diffe

2019-05-05 23:59发布

问题:

Based on a previous discussion I had in SO (see Doubts on concurrency with objects that can be used multiple times like formatters), here I'm asking a more theoretical question about objects that during the application lifetime are created once (and never modified, hence read-only) and they can be accessed from different threads. A simple use case it's the Core Data one. Formatters can be used in different threads (main thread, importing thread, etc.).

NSFormatters, for example, are extremely expensive to create. Based on that they can created once and then reused. A typical pattern that can be follow (also highlighted by @mattt in NSFormatter article) is the following.

+ (NSNumberFormatter *)numberFormatter {
    static NSNumberFormatter *_numberFormatter = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _numberFormatter = [[NSNumberFormatter alloc] init];
        [_numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
    });

    return _numberFormatter;
}

Even if I'm sure that is a very good approach to follow (a sort of read-only/immutable object is created), formatters are not thread safe and so using them in a thread safe manner could be dangerous. I found a discussion on the argument in NSDateFormatter crashes when used from different threads where the author has noticed that a crash could happen.

NSDateFormatters are not thread safe; there was a background thread attempting to use the same formatter at the same time (hence the randomness).

So, what could be the problem in accessing a formatter from different threads? Any secure pattern to follow?

回答1:

Specific answer for formatters:

Prior to iOS 7/OSX 10.9, even read-only access to formatters was not thread-safe. ICU has a ton of lazy computation that it does in response to requests, and that can crash or produce incorrect results if done concurrently.

In iOS 7/OSX 10.9, NSDateFormatter and NSNumberFormatter use locks internally to serialize access to the underlying ICU code, preventing this issue.

General answer:

Real-only access/immutable objects are indeed generally thread-safe, but it's difficult-to-impossible to tell which things actually are internally immutable, and which ones are merely presenting an immutable interface to the outside world.

In your own code, you can know this. When using other people's classes, you'll have to rely on what they document about how to use their classes safely.

(edit, since an example of serializing access to formatters was requested)

// in the dispatch_once where you create the formatter
dispatch_queue_t formatterQueue = dispatch_queue_create("date formatter queue", 0);

// where you use the formatter
dispatch_sync(formatterQueue, ^{ (do whatever you wanted to do with the formatter) });


回答2:

I'm asking a more theoretical question about objects that during the application lifetime are created once (and never modified, hence read-only)

That is not technically correct: in order for an object to be truly read-only, it must also be immutable. For example, one could create NSMutableArray once, but since the object allows changes after creation, it cannot be considered read-only, and is therefore unsafe for concurrent use without additional synchronization.

Moreover, logically immutable object could have mutating implementation, making them non-thread-safe. For example, objects that perform lazy initialization on first use, or cache state in their instance variables without synchronization, are not thread-safe. It appears that NSDateFormatter is one such class: it appears that you could get a crash even when you are not calling methods to mutate the state of the class.

One solution to this could be using thread-local storage: rather than creating one NSDateFormatter per application you would be creating one per thread, but that would let you save some CPU cycles as well: one report mentions that they managed to shave 5..10% off their start-up time by using this simple trick.