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中出现死锁?
为了扩展争用条件问题,真正的解决方法是在应用程序中不进行不确定的初始化。不确定或懒惰的初始化导致的行为很容易因看似无害的更改而改变——配置、"无关"代码更改等。
最好在程序生命周期中的已知良好点上显式初始化子系统。也就是说,如果你真的需要在程序早期初始化子系统(或者如果你想额外防御的话,甚至更早地移动它),就把
最好还是将初始化完全移出该方法。即
尽管我是一个便利爱好者,但25年的objc编程已经教会了我,懒惰的初始化是比它值得的更多维护和重构难题的来源。
虽然下面描述的竞争条件存在,但此代码不会修复下面描述的问题。几十年来,当我们不担心共享实例初始值设定项中的并发性时,它确实做到了。为繁荣留下了错误的代码。
记住,对于科林和哈拉尔德的正确答案,有一个非常微妙的种族条件,可能会导致你陷入一个痛苦的世界。
也就是说,如果正在分配的类的
这是您要将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+,特别是。在旧的操作系统中,如果块不可用,则使用锁或执行一次非基于块的操作的各种方法之一。
上面的模式实际上并不能阻止文本中描述的问题,并且会在遇到死锁时导致死锁。问题是,
最快的线程安全方法是使用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中调用
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; }); } |