我们为什么要分开alloc和init调用以避免Objective-C中的死锁?

时间:2014-01-03 01:51:58

标签: objective-c multithreading thread-safety initialization grand-central-dispatch

在阅读有关线程安全的单例时,我在SO上找到Thread safe instantiation of a singleton,并在接受的答案中使用此代码:

    sharedInstance = [MyClass alloc];
    sharedInstance = [sharedInstance init];

为什么要分离alloc和init方法?答案的作者写道:

  

即,如果正在分配的类的init碰巧调用sharedInstance方法,它将在设置变量之前执行此操作。在这两种情况下都会导致僵局。这是您想要将allocinit分开的一次。

有人可以详细向我解释这种分离的好处是什么?我无法理解作者的意思。在创建单例时,我是否真的需要分离allocinit方法调用,即使我在dispatch_once()中进行线程安全吗?

1 个答案:

答案 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语句没有线程安全。