12

When reading about thread-safe singletons I found Thread safe instantiation of a singleton here on SO, and in the accepted answer this code:

    sharedInstance = [MyClass alloc];
    sharedInstance = [sharedInstance init];

Why should we separate alloc and init methods? The author of the answer wrote:

Namely, if the init of the class being allocated happens to call the sharedInstance method, it will do so before the variable is set. In both cases it will lead to a deadlock. This is the one time that you want to separate the alloc and the init.

Can someone please explain to me in detail what the benefits of this separation are? I couldn't understand what the author meant at all. Do I really need to separate alloc and init methods calls when I create a singleton, even if I do it in dispatch_once() which is thread safe??

Community
  • 1
  • 1
MainstreamDeveloper00
  • 8,436
  • 15
  • 56
  • 102
  • 1
    The point that bbum is making is that `dispatch_once()` is "thread-safe" in that no more than one thread will be able to run the Block. The danger is that `dispatch_once()` cannot be called again from its Block -- it is not [reentrant](http://en.wikipedia.org/wiki/Reentrancy_(computing)). bbum's code -- as he has noted at the end -- doesn't actually prevent this problem. He knows a lot more than I do about this stuff, but I think he may be overly paranoid about this particular chance of deadlock. I think re-calling the `sharedInstance` method from `init` can be considered programmer error. – jscs Jan 03 '14 at 02:10
  • 2
    @JoshCaswell "The danger is that dispatch_once() cannot be called again from its Block" I really don't understand why we should do it anyway? – Oleksandr Karaberov Jan 03 '14 at 02:16
  • In this case it would only be as an error, @AlexanderKaraberov. – jscs Jan 03 '14 at 02:17
  • This mistake is easier to make than expected... Even if a class's `-init` method doesn't directly call it's own `+sharedInstance`, another object that you're allocating inside `-init` may call it. – indragie Jan 03 '14 at 02:18
  • 1
    I notified bbum in the original question. So I hope he can add more information about this "paranoid" separation – Oleksandr Karaberov Jan 03 '14 at 02:19
  • @indragie So your point is "another object that you're allocating inside -init may call it"You mean if I call "+sharedInstance" with no separated `alloc` and `init` in any other init methods I will have a deadlock bbmum talked about? – Oleksandr Karaberov Jan 03 '14 at 02:21
  • 1
    @AlexanderKaraberov Correct, if any code that runs inside `-init` calls through to `+sharedInstance` when using `dispatch_once()`, that will result in a deadlock. As long as you're NOT doing this, it doesn't matter whether you separate `+alloc`/`-init` or keep them together. See my answer for more information. – indragie Jan 03 '14 at 02:23
  • 1
    That's ridiculous -- there's no functional difference between making the two calls in one line or two, other than making it in two lines exposes the un-inited object via the "sharedInstance" variable (which should be automatic and local and hence "safe"). – Hot Licks Jan 03 '14 at 02:39
  • @HotLicks There's a lot of confusion here over @bbum's answer because the `+alloc`/`init` split was based on a previous version of his answer (that he has since edited) that included a check of `sharedInstance` before calling `dispatch_once`. See the edit to my answer for the original code. – indragie Jan 03 '14 at 02:44

1 Answers1

16

@bbum's post has been updated to mention that this solution does not solve the problem being described. Regardless of whether you separate +alloc and -init or not, this problem still exists.

The reasoning is in the edit to his post, but to expand on that, dispatch_once() is not reentrant. In this case, this means that calling dispatch_once() inside a dispatch_once() block (ie. recursively) will result in a deadlock.

So for example, if you have the following code for +sharedInstance:

+ (MyClass *)sharedInstance
{   
    static MyClass *sharedInstance = nil;
    static dispatch_once_t pred;

    dispatch_once(&pred, ^{
        sharedInstance = [[MyClass alloc] init]
    });

    return sharedInstance;
}

..and MyClass's -init method directly or indirectly also calls through to its own +sharedInstance class method (e.g. maybe some other object that MyClass -init allocates calls through to MyClass's +sharedInstance), that would mean that you are attempting to call dispatch_once from inside itself.

Since dispatch_once is thread safe, synchronous, and designed such that it executes exactly once, you can not invoke dispatch_once again before the block inside has finished executing once. Doing so will result in a deadlock, because the second call of dispatch_once will be waiting for the first call (already in the middle of execution) to complete, while the first call is waiting on the second (recursive) call to dispatch_once to go through. They are waiting on each other, hence there's a deadlock.

If you want a solution that provides reentrancy, you would need to use something like NSRecursiveLock which is considerably more expensive than dispatch_once, which doesn't use a locking mechanism.

EDIT: Reasoning for the split of +alloc/-init in @bbum's original answer as requested:

The original code @bbum posted before editing it looked like this:

+ (MyClass *)sharedInstance
{   
    static MyClass *sharedInstance = nil;
    static dispatch_once_t pred;

    if (sharedInstance) return sharedInstance;

    dispatch_once(&pred, ^{
        sharedInstance = [MyClass alloc];
        sharedInstance = [sharedInstance init];
    });

    return sharedInstance;
}

Note this line: if (sharedInstance) return sharedInstance;

The idea here is that assigning a non-nil value to sharedInstance before calling -init would result in the existing value of sharedInstance (returned from +alloc) being returned before hitting the dispatch_once() call (and avoiding the deadlock) in the case that the -init call results in a recursive call to +sharedInstance as discussed earlier in my answer.

However, this is a brittle fix because the if statement there is not thread-safe.

Community
  • 1
  • 1
indragie
  • 18,002
  • 16
  • 95
  • 164
  • Ok.+1.This clarified a lot. But this words bother me: 'this solution no longer solve the problem being described:`How can it solve the problem earlier?. Words `no longer` said about it.Are there some changes to alloc init behavior since then? (2010)? – Oleksandr Karaberov Jan 03 '14 at 02:28
  • @AlexanderKaraberov Edited my answer to include the reasoning for that. Hope it helps! – indragie Jan 03 '14 at 02:37
  • Thanks. This is really an interesting issue. I hope your answer will be accepted or bbum will come here and add some more information:) – Oleksandr Karaberov Jan 03 '14 at 02:45
  • 1
    One more question. You said:"If you want a solution that provides reentrancy, you would need to use something like NSRecursiveLock which is considerably more expensive than dispatch_once, which doesn't use a locking mechanism." But we use this lock in a synchronous method, so why can it be expensive?Can we just make an if statement in your edit thread safe with it? – Oleksandr Karaberov Jan 03 '14 at 02:53
  • 1
    @AlexanderKaraberov The `dispatch_once()` solution is designed to be thread safe. If you want similar thread safety **with** reentrancy, that's what `NSRecursiveLock` is for. If you use neither a lock nor `dispatch_once()`, `+sharedInstance` is no longer thread safe and you must be sure to not call it on more than one thread simultaneously. I say `NSRecursiveLock` is more expensive because `NSRecursiveLock` actually uses a locking mechanism whereas `dispatch_once()` does not. Using a lock is more expensive than not using one. – indragie Jan 03 '14 at 02:58
  • 2
    That split implementation seems like very dangerous code. If a second thread accesses the sharedInstance before `init` has completed it will get the *uninitialized* object. There's no way this could be goodness. – Hot Licks Jan 03 '14 at 03:00
  • @AlexanderKaraberov If you need more information on why it's more expensive to use a lock you may find this useful: http://en.wikipedia.org/wiki/Lock_(computer_science)#Granularity – indragie Jan 03 '14 at 03:00
  • Hot Licks: You're right, that's why @bbum edited his answer (and he mentioned that it was a poor solution in the comments as well). I think he should've removed that answer entirely to prevent confusion like this. – indragie Jan 03 '14 at 03:02
  • And if the performance of the first-time-through mechanism is an issue, singletons are being used far too frequently. – Hot Licks Jan 03 '14 at 03:04
  • Perhaps the safest choice is to go back to using +initialize, which is guaranteed to be executed exactly once and before any other method in the class is called. – EricS Jan 03 '14 at 03:33
  • @EricS: `+initialize` is not guaranteed to be executed once; the runtime only guarantees that _it_ will _send the message_ exactly once to a particular class. Without a guard, it can be run more than once when a subclass does not override it (or in case of a programmer making an error and for some reason sending `initialize` manually). Whether this is more or less likely than the case we're worrying about here, I don't know. – jscs Jan 03 '14 at 04:13
  • I didn't want to get into that level of detail in a short comment, but it's trivial to check the class type in +initialize. But, yes, it's definitely necessary. – EricS Jan 03 '14 at 05:58
  • 3
    @HotLicks is correct; it is danger code *now*, but wasn't considered dangerous when we first adopted that pattern in the early 90s as NeXTSTEP programs very rarely used threading beyond localized computation engines. There was very little call to ensure thread safety in something like `sharedInstance` because it was exceedingly rare that the first invocation would even be called when the app had gone multithreaded (there was a reason for that Foundation notification indicating when an app "went multithreaded"). Different times. – bbum Jan 03 '14 at 18:09