线程安全实例化单例

Mac*_*uch 33 singleton multithreading memory-management objective-c thread-safety

使用哪种同步方法来确保单例仍然是单例?

+(Foo*)sharedInstance
{
   @synchronized(self)
   {
      if (nil == _sharedInstance)
      {
         _sharedInstance = [[Foo alloc] init];
         ...
      }
   }
   return _sharedInstance;
}
Run Code Online (Sandbox Code Playgroud)

还是使用互斥?

#import <pthread.h>

static pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER;

+(Foo*)sharedInstance
{
   pthread_mutex_lock(&_mutex);
   if (nil == _sharedInstance)
   {
      _sharedInstance = [[Foo alloc] init];
      ...
   }
   pthread_mutex_unlock(&_mutex);
   return _sharedInstance;
}
Run Code Online (Sandbox Code Playgroud)

嗯..对此有何评论?

bbu*_*bum 56

请务必阅读有关此问题/答案的讨论. 我们为什么要分开alloc和init调用以避免Objective-C中的死锁?


扩大竞争条件问题; 在真正的解决办法是你的应用程序中没有不确定初始化. 不确定懒惰的初始化会导致行为很容易因看似无害的变化而改变 - 配置,"不相关"的代码更改等等......

最好在程序的生命周期中明确初始化已知良好点上的子系统.即拖放[MyClass sharedInstance];到你的应用程序委托的applicationDidFinishLaunching:方法,如果你真的需要一个子系统,在项目早期进行初始化(甚至更早移动它,如果你想成为额外的防御).

最好还是完全从该方法中移出初始化.即[MyClass initializeSharedInstance];,+sharedInstance如果不首先调用该方法,则asserts().

尽管我是一个方便的粉丝,25年的ObjC编程告诉我,懒惰的初始化是更多维护和重构头痛的源泉,而不是它的价值.


虽然存在下面描述的竞争条件,但是该代码不能解决下面描述的内容.几十年来我们不担心共享实例初始化器中的并发性.为繁荣留下错误的代码.

请记住,对于科林和哈拉德的正确答案,有一种非常微妙的竞争条件可能会让你陷入悲痛的世界.

也就是说,如果-init正在分配的类恰好调用该sharedInstance方法,它将在设置变量之前执行此操作.在这两种情况下都会导致僵局.

这是您想要分离alloc和init的一次.克服Colin的代码因为它是最好的解决方案(假设是Mac OS X):

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

    // partial fix for the "new" concurrency issue
    if (sharedInstance) return sharedInstance;
    // partial because it means that +sharedInstance *may* return an un-initialized instance
    // this is from https://stackoverflow.com/questions/20895214/why-should-we-separate-alloc-and-init-calls-to-avoid-deadlocks-in-objective-c/20895427#20895427

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

    return sharedInstance;
}
Run Code Online (Sandbox Code Playgroud)

请注意,这仅适用于Mac OS X; 特别是X 10.6+和iOS 4.0+.在没有块的旧操作系统上,使用锁定或一种不基于块的操作的方法之一.


上述模式实际上并不能防止文本中描述的问题,并且在遇到问题时会导致死锁.问题是,这dispatch_once()不是可重入的,因此,如果init呼叫sharedInstance,楔入城市.

  • @bbum,(1)为什么`-init`调用`+ sharedInstance`,以及(2)为什么要包含`if(sharedInstance)行返回sharedInstance;`? (4认同)
  • 仅为了完整性 - 以上适用于iOS 4及更高版本的块在iOS上引入时. (4认同)
  • 我建议删除"if(sharedInstance)返回sharedInstance",它是Double Checked Locking并且不起作用(参见http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf) (3认同)
  • 你为什么编辑你的答案来删除使其有价值的单一信息?您编辑的代码与Colin的原始答案没有什么不同,现在您的贡献毫无意义,您的代码不再与您的文本相对应(您的答案所在的alloc和init之间的区别在哪里?).如果,在事后的想法中,您觉得您的答案无效,那么(1)请添加一条说明,而不是让您的答案不一致;(2)请解释为什么它会无效.谢谢! (3认同)

Col*_*ler 38

最快的线程安全方法是使用Grand Central Dispatch(libdispatch)和dispatch_once()

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

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

    return sharedInstance;
}
Run Code Online (Sandbox Code Playgroud)


Arv*_*vin 12

如果有人关心,这里有一个宏相同的东西:

   /*!
    * @function Singleton GCD Macro
    */
    #ifndef SINGLETON_GCD
    #define SINGLETON_GCD(classname)                            \
                                                                \
    + (classname *)shared##classname {                          \
                                                                \
        static dispatch_once_t pred;                            \
        static classname * shared##classname = nil;             \
        dispatch_once( &pred, ^{                                \
            shared##classname = [[self alloc] init];            \
        });                                                     \
        return shared##classname;                               \
    }                                                           
    #endif
Run Code Online (Sandbox Code Playgroud)