Mike Ash Singleton:放置@synchronized

fuz*_*oat 7 cocoa objective-c double-checked-locking

我在Mike Ash的"照顾和喂养单身人士"中遇到了这个问题,他的评论有点困惑:

不过,这段代码有点慢.拿锁是有点贵的.令人痛苦的是,在绝大多数情况下,锁定毫无意义.只有当foo为nil时才需要锁定,这基本上只发生一次.在单例初始化之后,对锁的需求消失了,但锁本身仍然存在.

+(id)sharedFoo {
    static Foo *foo = nil;
    @synchronized([Foo class]) {
        if(!foo) foo = [[self alloc] init];
    }
    return foo;
}
Run Code Online (Sandbox Code Playgroud)

我的问题是,毫无疑问这是一个很好的理由,但为什么你不能写(见下文)来限制当foo为零时的锁定?

+(id)sharedFoo {
    static Foo *foo = nil;
    if(!foo) {
        @synchronized([Foo class]) {
            foo = [[self alloc] init];
        }
    }
    return foo;
}
Run Code Online (Sandbox Code Playgroud)

欢呼加里

小智 18

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

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


mfa*_*kas 7

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

Mike Ash也使用volatile显示正确的解决方案OSMemoryBarrier();.

问题是当一个线程执行时foo = [[self alloc] init];,无法保证当另一个线程看到foo != 0所执行的所有内存写入时init也是可见的.

另请参阅DCL和C++以及DCL和java以获取更多详细信息.

  • dispatch_once是真正的解决方案,只需使用它并退出黑客攻击 (3认同)