我在迈克·阿什(Mike Ash)的“单身人士的护理和喂养”中看到了这一点,并对他的评论感到有点困惑:

不过,这段代码有点慢。获取锁有点昂贵。使它更加痛苦的事实是,绝大多数时间毫无意义。仅当foo为零时,才需要锁,这基本上只发生一次。初始化单元后,对锁的需求消失了,但锁本身仍然存在。

+(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;
}

干杯加里

有帮助吗?

解决方案

由于则测试是受争用条件。两个不同的线程可能会独立地测试foonil,且然后(序贯)创建单独的实例。这可以在你的修改版本发生当一个线程执行测试,而其他还在里面+[Foo alloc]-[Foo init],但尚未设定foo

顺便说一句,我不会那样做的。退房dispatch_once()功能,它可以让你保证你的应用程序的生命周期内的块只会被执行一次(假设你有GCD您指定的平台)。

其他提示

这被称为 双重检查锁定“优化”. 。正如各地记录的那样,这是不安全的。即使它没有被编译器优化击败,它也会以现代机器上内存的工作方式被击败,除非您使用某种栅栏/屏障。

迈克·阿什还展示了 正确的解决方案使用 volatileOSMemoryBarrier();.

问题是当一个线程执行时 foo = [[self alloc] init]; 不能保证当其他线程看到时 foo != 0 所有内存写入执行 init 也是可见的。

另请参阅 DCL 和 C++DCL 和 java 更多细节。

在您的版本为!foo检查可以发生在同时多线程,允许两个线程跳进alloc块,一个等待其它分配到另一个实例之前完成。

如果FOO ==为零,但在那之后,你需要再次测试(内@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;
}

如果你有大中央调度的最佳方法

+ (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];
 }
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top