Code execution stops on using thread safe Singleton initialization code

旧巷老猫 提交于 2019-12-13 15:20:24

问题


To make use of global variables and methods I implemented Singleton as a healthy coding practice. I followed Apple documents, john wordsworth blog before implementing it. In the first, I did not make my singleton thread safe and I implemented this method along with all other mentioned in the blog and Apple document.

+ (SingletonClass *)sharedManager 
{
  static SingletonClass *sharedManager = nil;
  if (sharedManager == nil) {
    sharedManager = [[super allocWithZone:NULL] init];
}
  return sharedManager;
}

After that to make Singleton thread safe I made changes to + (SingletonClass *)sharedManager class like this and my app stops launching. I put break points and observed dispatch_once gets called twice and then code stops executing further.

+(SingletonClass *)sharedManager
{
  static SingletonClass *sharedManager = nil;
  if (sharedManager !=nil)
  {
    return sharedManager;
  }
  static dispatch_once_t pred;       
  dispatch_once(&pred, ^{
    sharedManager = [SingletonClass alloc];
    sharedManager=[sharedManager init];
});

     return sharedManager;
}

If i remove this thread safe code snippet and revert back to previous code it works fine and code gets executed.

Please note that I also looked at the bbum's answer here in which he has mentioned possible deadlock situation before asking question but I am not able to figure out the issue. Any explanation or solution will be helpful for me. Thanks.

Edit 1:

In case someone wants to look at the complete code, I have created gist for that. Please follow there. Thanks.


回答1:


Let's consider what happens if two threads call second version of sharedManager almost simultaneously.

Thread 1 calls first. It checks sharedManager !=nil, which is false, so it goes on to the dispatch_once. In the dispatch_once block, it executes [SingletonClass alloc] and stores the result in sharedManager.

Now, before thread 1 continues on to the next line, thread 2 comes along and calls sharedManager. Thread 2 checks sharedManager !=nil, which is now true. So it returns sharedManager, and the caller then tries to use sharedManager. But at this time, sharedManager hasn't been fully initialized yet. That's bad.

You cannot set sharedManager until you have a fully initialized object to set it to. Also (as borrrden pointed out), you don't need the sharedManager !=nil check at the top, because dispatch_once is very efficient anyway.

+ (SingletonClass *)sharedManager {
    static dispatch_once_t pred;
    static SingletonClass *sharedManager;
    dispatch_once(&pred, ^{
        sharedManager = [[SingletonClass alloc] init];
    });
    return sharedManager;
}

Now, I've looked at your gist and your problem is here:

+ (id)allocWithZone:(NSZone*)zone {
    return [[self sharedManager] retain];
}

Your +[SingletonClass sharedManager] method calls +[SingletonClass alloc] in the dispatch_once block. Since you don't override alloc, +[SingletonClass alloc] calls +[SingletonClass allocWithZone:NULL]. And +[SingletonClass allocWithZone:] method calls +[SingletonClass sharedManager]. On this second call to sharedManager, your program hangs in dispatch_once, because you're still inside the first call to dispatch_once.

The simplest fix is to remove your implementation of allocWithZone:. Just document that sharedManager is the only supported way to get an instance of SingletonClass and move on.

If you want to be obtuse and make [[SingletonClass alloc] init] return the singleton, even if you do it repeatedly, it's complicated. Don't try to override alloc or allocWithZone:. Do this:

static SingletonClass *sharedManager; // outside of any method

+ (SingletonClass *)sharedManager {
    return sharedManager ? sharedManager : [[SingletonClass alloc] init];
}

- (id)init {
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        if (self = [super init]) {
            // initialization here...
            sharedManager = self;
        }
    });
    self = sharedManager;
    return self;
}



回答2:


You don't need the check on the top, get rid of the if statement. The dispatch_once guarantees that the block will only be executed once in the lifetime of the application so the first check is redundant.

More Info: http://cocoasamurai.blogspot.jp/2011/04/singletons-your-doing-them-wrong.html



来源:https://stackoverflow.com/questions/14192140/code-execution-stops-on-using-thread-safe-singleton-initialization-code

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!