【问题标题】:Code execution stops on using thread safe Singleton initialization code代码执行在使用线程安全的 Singleton 初始化代码时停止
【发布时间】:2013-01-07 08:17:00
【问题描述】:

为了利用全局变量和方法,我将 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;
}

如果我删除这个线程安全代码 sn-p 并恢复到以前的代码,它可以正常工作并且代码被执行。

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

编辑 1:

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

【问题讨论】:

    标签: iphone objective-c ios singleton


    【解决方案1】:

    让我们考虑一下如果两个线程几乎同时调用第二个版本的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] 方法在dispatch_once 块中调用+[SingletonClass alloc]。由于您没有覆盖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;
    }
    

    【讨论】:

    • 感谢您对代码的详细解释。我首先写了sharedManager = [[SingletonClass alloc] init];,然后根据我提到的bbum 的回答将它分成两行。你是对的问题在其他地方。如果有人想查看完整代码,还发布了完整代码的链接。
    • 太棒了.. 正在工作.. 感谢您花时间研究完整的代码。接受作为答案..
    【解决方案2】:

    您不需要顶部的检查,摆脱if 语句。 dispatch_once 保证该块在应用程序的生命周期内只执行一次,因此第一次检查是多余的。

    更多信息: http://cocoasamurai.blogspot.jp/2011/04/singletons-your-doing-them-wrong.html

    【讨论】:

    • 值得补充:dispatch_once 很快。至少与“零”检查一样快。不同之处在于有人编写了一些非常聪明的代码来使其线程安全,而“nil”的检查则不是。在最坏的情况下,sharedSingleton 可能会返回 nil 以及编写的代码(如果在分配之前,当 sharedSingleton 仍然为 nil 时第二次调用进入,正在运行的 dispatch_once 完成,dispatch_once 什么也不做,因为第一个 dispatch_once 已经完成,并且因为 sharedSingleton不是 volatile 编译器假定它仍然是 nil 并返回 nil。
    猜你喜欢
    • 2012-02-27
    • 1970-01-01
    • 1970-01-01
    • 2021-10-01
    • 1970-01-01
    • 2012-05-31
    • 2012-07-13
    • 1970-01-01
    相关资源
    最近更新 更多