使用线程安全的Singleton初始化代码停止代码执行

时间:2013-01-07 08:17:01

标签: iphone objective-c ios singleton

为了利用全局变量和方法,我将Singleton实现为一种健康的编码实践。我在执行之前跟踪了Apple documentsjohn wordsworth blog。首先,我没有使我的单例线程安全,我实现了这个方法以及博客和Apple文档中提到的所有其他方法。

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

之后为了使Singleton线程安全,我对+ (SingletonClass *)sharedManager类进行了更改,我的应用程序停止启动。我把断点和观察dispatch_once两次调用,然后代码停止执行。

+(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;
}

如果我删除此线程安全代码段并恢复到以前的代码,它可以正常工作并执行代码。

请注意我在询问问题之前还查看了bbum's answer here中他提到可能出现的死锁情况,但我无法弄清楚问题。任何解释或解决方案对我都有帮助。感谢。

  

编辑1:

如果有人想查看完整的代码,我已为此创建了gist。请跟着那里。感谢。

1 个答案:

答案 0 :(得分:7)

让我们考虑如果两个线程几乎同时调用sharedManager的第二个版本会发生什么。

首先调用线程1。它会检查sharedManager !=nil,这是假的,因此会继续dispatch_once。在dispatch_once块中,它会执行[SingletonClass alloc]并将结果存储在sharedManager中。

现在,在线程1继续到下一行之前,线程2出现并调用sharedManager。线程2检查sharedManager !=nil,现在是真的。因此它返回sharedManager,然后调用者尝试使用sharedManager。但此时,sharedManager尚未完全初始化。那很糟糕。

在您设置完全初始化对象之前,无法设置sharedManager。另外(正如borrrden指出的那样),您不需要在顶部进行sharedManager !=nil检查,因为dispatch_once无论如何都非常有效。

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

现在,我看了你的要点,你的问题就在这里:

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

您的+[SingletonClass sharedManager]方法会在+[SingletonClass alloc]块中调用dispatch_once。由于您未覆盖alloc+[SingletonClass alloc]会调用+[SingletonClass allocWithZone:NULL]+[SingletonClass allocWithZone:]方法调用+[SingletonClass sharedManager]。在第二次拨打sharedManager时,您的程序会在dispatch_once中挂起,因为您仍然在第一次拨打dispatch_once时。

最简单的解决方法是删除allocWithZone:的实施。只需记录sharedManager是获取SingletonClass实例并继续前进的唯一受支持方式。

如果你想要迟钝并让[[SingletonClass alloc] init]返回单身,即使你反复做,也很复杂。请勿尝试覆盖allocallocWithZone:。这样做:

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;
}