Mike Ash Singleton:放置@synchronized

时间:2010-03-10 16:53:39

标签: objective-c cocoa double-checked-locking

我在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;
}
欢呼加里

5 个答案:

答案 0 :(得分:18)

因为那时测试受到竞争条件的影响。两个不同的线程可以独立测试foonil,然后(顺序)创建单独的实例。当一个线程执行测试而另一个线程仍在+[Foo alloc]-[Foo init]内但尚未设置foo时,您的修改版本可能会发生这种情况。

顺便说一下,我根本不会这样做。查看dispatch_once()功能,该功能可以保证在您的应用有效期内只执行一次阻止(假设您在目标平台上安装了GCD)。

答案 1 :(得分:7)

这称为double checked locking "optimization"。据说到处都有,这是不安全的。即使它没有被编译器优化打败,它也会被打败,就像现代机器上的内存一样,除非你使用某种栅栏/障碍。

Mike Ash also shows使用volatileOSMemoryBarrier();的正确解决方案。

问题在于,当一个线程执行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];
 }