线程安全实例化单例

时间:2010-02-04 11:06:26

标签: objective-c multithreading memory-management thread-safety singleton

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

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

还是使用互斥?

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

嗯......对此有何评论?

5 个答案:

答案 0 :(得分:56)

请务必阅读有关此问题/答案的讨论。 Why should we separate alloc and init calls to avoid deadlocks in Objective-C?


扩大竞争条件问题; 真正的修复程序是在您的应用程序中没有不确定的初始化。 不确定延迟初始化导致的行为很容易因看似无害的更改而改变 - 配置,“无关”代码更改等...

最好在程序的生命周期中明确初始化已知良好点上的子系统。即如果确实需要在程序的早期初始化该子系统(或者如果您想要更加防御,则将其移动到更早的位置),将[MyClass sharedInstance];放入您的App委托的applicationDidFinishLaunching:方法中。

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

尽管我是一个方便的粉丝,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;
}

注意这仅适用于Mac OS X;特别是X 10.6+和iOS 4.0+。在较旧的操作系统上,如果没有块,请使用锁或其中一种方法,即在不基于块的情况下执行某些操作。


上述模式实际上并没有阻止文本中描述的问题,并且在遇到问题时会导致死锁。问题是,dispatch_once()不是可重入的,因此init调用sharedInstance楔入城市

答案 1 :(得分: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;
}

答案 2 :(得分: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

答案 3 :(得分:2)

CocoaDev page可能对您有用。

答案 4 :(得分:1)

如果有人关心,这是同一件事的另一个宏:)

恕我直言,与the other variations相比,它提供了更大的灵活性。

#define SHARED_INSTANCE(...) ({\
    static dispatch_once_t pred;\
    static id sharedObject;\
    dispatch_once(&pred, ^{\
        sharedObject = (__VA_ARGS__);\
    });\
    sharedObject;\
})

用法,单行初始化:

+ (instancetype) sharedInstanceOneLine {
    return SHARED_INSTANCE( [[self alloc] init] );
}

用法,多行初始化(注意代码块周围的花括号):

+ (instancetype) sharedInstanceMultiLine {
    return SHARED_INSTANCE({
        NSLog(@"creating shared instance");
        CGFloat someValue = 84 / 2.0f;
        [[self alloc] initWithSomeValue:someValue]; // no return statement
    });
}

在作业的正确部分使用:

- (void) someMethod {
    MethodPrivateHelper *helper = SHARED_INSTANCE( [[MethodPrivateHelper alloc] init] );
    // do smth with the helper
}
// someMethod should not call itself to avoid deadlock, see bbum's answer

此修改使用了两种语言功能:GCC compound expressions扩展(Clang也支持)和C99 variadic macros support

预处理后,输出看起来像(您可以通过在Xcode 5中调用Product > Perform Action > Preprocess "YourClassName.m"来自行测试):

+ (instancetype) sharedInstanceOneLine {
    return ({
        static dispatch_once_t pred;
        static id sharedObject;
        dispatch_once(&pred, ^{
            sharedObject = ( [[self alloc] init] );
        });
        sharedObject; // this object will be returned from the block
    });
}

+ (instancetype) sharedInstanceMultiLine {
    return ({
        static dispatch_once_t pred;
        static id sharedObject;
        dispatch_once(&pred, ^{
            sharedObject = ({
                NSLog(@"creating shared instance");
                CGFloat someValue = 84 / 2.0f;
                [[self alloc] initWithSomeValue:someValue];
            });
        });
        sharedObject;
    });
}

- (void) someMethod {
    MethodPrivateHelper *helper = ({
        static dispatch_once_t pred;
        static id sharedObject;
        dispatch_once(&pred, ^{
            sharedObject = ( [[MethodPrivateHelper alloc] init] );
        });
        sharedObject;
    });
}