在阅读有关线程安全的单例时,我在SO上找到Thread safe instantiation of a singleton,并在接受的答案中使用此代码:
sharedInstance = [MyClass alloc];
sharedInstance = [sharedInstance init];
为什么要分离alloc和init方法?答案的作者写道:
即,如果正在分配的类的
init
碰巧调用sharedInstance
方法,它将在设置变量之前执行此操作。在这两种情况下都会导致僵局。这是您想要将alloc
和init
分开的一次。
有人可以详细向我解释这种分离的好处是什么?我无法理解作者的意思。在创建单例时,我是否真的需要分离alloc
和init
方法调用,即使我在dispatch_once()
中进行线程安全吗?
答案 0 :(得分:16)
@bbum's post已更新,提及此解决方案无法解决所描述的问题。无论您是否将+alloc
和-init
分开,此问题仍然存在。
推理是在他的帖子的编辑中,但为了扩展,dispatch_once()
不是reentrant。在这种情况下,这意味着在dispatch_once()
块中调用dispatch_once()
+sharedInstance
块(即递归)将导致死锁。
例如,如果您有+ (MyClass *)sharedInstance
{
static MyClass *sharedInstance = nil;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
sharedInstance = [[MyClass alloc] init]
});
return sharedInstance;
}
的以下代码:
MyClass
..和-init
的{{1}}方法直接或间接地调用自己的+sharedInstance
类方法(例如,MyClass -init
可以分配调用的其他对象到MyClass
的{{1}}),这意味着您试图从内部调用+sharedInstance
。
由于dispatch_once
是线程安全的,同步的和设计的,因此它只执行一次,所以在内部块执行一次之前,不能再次调用dispatch_once
。这样做会导致死锁,因为dispatch_once
的第二次调用将等待第一次调用(已经在执行中)完成,而第一次调用正在等待第二次(递归)调用dispatch_once
要经过。他们互相等待,因此陷入僵局。
如果你想要一个提供可重入性的解决方案,你需要使用类似NSRecursiveLock
的东西,它比不使用锁定机制的dispatch_once
贵得多。
编辑:根据要求推断@ bbum原始答案中dispatch_once
/ +alloc
的分割:
在编辑之前发布的原始代码@bbum看起来像这样:
-init
请注意以下一行:+ (MyClass *)sharedInstance
{
static MyClass *sharedInstance = nil;
static dispatch_once_t pred;
if (sharedInstance) return sharedInstance;
dispatch_once(&pred, ^{
sharedInstance = [MyClass alloc];
sharedInstance = [sharedInstance init];
});
return sharedInstance;
}
这里的想法是在调用if (sharedInstance) return sharedInstance;
之前为sharedInstance
分配一个非零值将导致返回-init
(从sharedInstance
返回)的现有值之前点击+alloc
调用(并避免死锁),如果dispatch_once()
调用导致对-init
的递归调用,如我在前面的回答中所讨论的那样
但是,这是一个脆弱的修复,因为+sharedInstance
语句没有线程安全。