关于objective c:单例的线程安全实例化

Thread safe instantiation of a singleton

要使用哪一种同步方法来确保单例保留为单例?

1
2
3
4
5
6
7
8
9
10
11
12
+(Foo*)sharedInstance
{
   @synchronized(self)
   {
      if (nil == _sharedInstance)
      {
         _sharedInstance = [[Foo alloc] init];
         ...
      }
   }
   return _sharedInstance;
}

或者使用互斥?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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;
}

六羟甲基三聚氰胺六甲醚。。对此有何评论?


确保你也阅读了关于这个问题/答案的讨论。为什么要分离alloc和init调用以避免在objective-c中出现死锁?


为了扩展争用条件问题,真正的解决方法是在应用程序中不进行不确定的初始化。不确定或懒惰的初始化导致的行为很容易因看似无害的更改而改变——配置、"无关"代码更改等。

最好在程序生命周期中的已知良好点上显式初始化子系统。也就是说,如果你真的需要在程序早期初始化子系统(或者如果你想额外防御的话,甚至更早地移动它),就把[MyClass sharedInstance];放到你的应用委托的applicationDidFinishLaunching:方法中。

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

尽管我是一个便利爱好者,但25年的objc编程已经教会了我,懒惰的初始化是比它值得的更多维护和重构难题的来源。


虽然下面描述的竞争条件存在,但此代码不会修复下面描述的问题。几十年来,当我们不担心共享实例初始值设定项中的并发性时,它确实做到了。为繁荣留下了错误的代码。

记住,对于科林和哈拉尔德的正确答案,有一个非常微妙的种族条件,可能会导致你陷入一个痛苦的世界。

也就是说,如果正在分配的类的-init碰巧调用sharedInstance方法,它将在设置变量之前调用该方法。在这两种情况下,都会导致死锁。

这是您要将alloc和init分开的一次。抄写科林的代码,因为它是最好的解决方案(假设是Mac OS X):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+(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()不是再入者,因此,如果initsharedInstance为楔形城市。


最快的线程安全方法是使用GrandCentralDispatch(libDispatch)和Dispatch_Once()。

1
2
3
4
5
6
7
8
9
10
11
+(MyClass *)sharedInstance
{  
    static MyClass *sharedInstance = nil;
    static dispatch_once_t pred;

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

    return sharedInstance;
}


如果有人关心,这里有一个宏用于相同的事情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   /*!
    * @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

这个cocadev页面可以满足您的需要。


如果有人关心,这里有另一个宏用于相同的事情:)

imho,与其他变体相比,它提供了更大的灵活性。

1
2
3
4
5
6
7
8
#define SHARED_INSTANCE(...) ({\
    static dispatch_once_t pred;\
    static id sharedObject;\
    dispatch_once(&pred, ^{\
        sharedObject = (__VA_ARGS__);\
    });\
    sharedObject;\
})

用法,单行初始化:

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

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

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

在分配的正确部分中的用法:

1
2
3
4
5
- (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复合表达式扩展(clang也支持它)和c99 variadic宏支持。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
+ (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;
    });
}