我在Mike Ash的“照顾和喂养单身人士”中遇到了这个问题,并且对他的评论感到有点困惑:
但是这段代码有点慢。 拿锁是有点贵的。 使事情变得更加痛苦 绝大多数时候, 锁是没有意义的。锁是 只有当foo为零时才需要 基本上只发生一次。之后 singleton初始化,需要 锁已经消失,但锁本身 仍然存在。
+(id)sharedFoo {
static Foo *foo = nil;
@synchronized([Foo class]) {
if(!foo) foo = [[self alloc] init];
}
return foo;
}
我的问题是,毫无疑问这是一个很好的理由,但为什么你不能写(见下文)来限制当foo为零时的锁定?
+(id)sharedFoo {
static Foo *foo = nil;
if(!foo) {
@synchronized([Foo class]) {
foo = [[self alloc] init];
}
}
return foo;
}
欢呼加里
答案 0 :(得分:18)
因为那时测试受到竞争条件的影响。两个不同的线程可以独立测试foo
是nil
,然后(顺序)创建单独的实例。当一个线程执行测试而另一个线程仍在+[Foo alloc]
或-[Foo init]
内但尚未设置foo
时,您的修改版本可能会发生这种情况。
顺便说一下,我根本不会这样做。查看dispatch_once()
功能,该功能可以保证在您的应用有效期内只执行一次阻止(假设您在目标平台上安装了GCD)。
答案 1 :(得分:7)
这称为double checked locking "optimization"。据说到处都有,这是不安全的。即使它没有被编译器优化打败,它也会被打败,就像现代机器上的内存一样,除非你使用某种栅栏/障碍。
Mike Ash also shows使用volatile
和OSMemoryBarrier();
的正确解决方案。
问题在于,当一个线程执行foo = [[self alloc] init];
时,无法保证当另一个线程看到foo != 0
init
执行的所有内存写入时也是可见的。
另请参阅DCL and C++和DCL and java了解详情。
答案 2 :(得分:1)
在您的版本中,!foo
的检查可能同时发生在多个线程上,允许两个线程跳转到alloc
块,一个等待另一个完成,然后再分配另一个实例
答案 3 :(得分:1)
如果foo == nil,你只能通过锁定进行优化,但之后你需要再次测试(在@synchronized内)以防止竞争条件。
+ (id)sharedFoo {
static Foo *foo = nil;
if(!foo) {
@synchronized([Foo class]) {
if (!foo) // test again, in case 2 threads doing this at once
foo = [[self alloc] init];
}
}
return foo;
}
答案 4 :(得分:1)
如果您有宏观的中央调度,最好的方法
+ (MySingleton*) instance {
static dispatch_once_t _singletonPredicate;
static MySingleton *_singleton = nil;
dispatch_once(&_singletonPredicate, ^{
_singleton = [[super allocWithZone:nil] init];
});
return _singleton
}
+ (id) allocWithZone:(NSZone *)zone {
return [self instance];
}