Thoughts in accessing read only objects from different threads
Asked Answered
M

2

5

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?

Monitorial answered 13/11, 2013 at 17:21 Comment(2)
Most likely the formatter has some state that is modified as it's doing its work, so multiple concurrent calls to it end up mangling the state. The secure pattern is to either create one formatter per thread, or use some kind of mutual exclusion to prevent multiple threads from accessing a particular formatter concurrently.Thankyou
just because you don't explicitly modify a formatter does not mean that it doesnt have an internal state that gets modified though use. That said, I'm pretty sure I have done it without problems; probably depends on your exact usage.Fasciate
O
8

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) });
Oocyte answered 13/11, 2013 at 17:35 Comment(4)
(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) });Oocyte
That is interesting information about the different behavior in iOS/OS X versions. Is this documented somewhere?Kailey
The Foundation release notes for 10.9 discuss it. I've filed a documentation radar to get the docs updated, but so far it hasn't been done.Oocyte
Thank you very much!! Very nice dscussion. Cheers.Monitorial
E
3

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.

Ecstatics answered 13/11, 2013 at 17:40 Comment(1)
The TLS solution is a solid one if you're expecting a lot of contention on the formatter. Given the relative speed of formatting vs creating formatters, though, serialization is usually going to be more efficient.Oocyte

© 2022 - 2024 — McMap. All rights reserved.