Mike Ash Singleton:放置@synchronized

发布于 2024-08-24 05:36:26 字数 656 浏览 6 评论 0原文

我在迈克·阿什(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;
}

干杯加里

I came accross this on the Mike Ash "Care and feeding of singletons" and was a little puzzeled by his comment:

This code is kind of slow, though.
Taking a lock is somewhat expensive.
Making it more painful is the fact
that the vast majority of the time,
the lock is pointless. The lock is
only needed when foo is nil, which
basically only happens once. After the
singleton is initialized, the need for
the lock is gone, but the lock itself
remains.

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

My question is, and there is no doubt a good reason for this but why can't you write (see below) to limit the lock to when foo is nil?

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

cheers gary

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

调妓 2024-08-31 05:36:26

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

顺便说一句,我根本不会那样做。查看 dispatch_once() 函数,它可以让您保证一个块在应用程序的生命周期内只执行一次(假设您在目标平台上有 GCD)。

Because then the test is subject to a race condition. Two different threads might independently test that foo is nil, and then (sequentially) create separate instances. This can happen in your modified version when one thread performs the test while the other is still inside +[Foo alloc] or -[Foo init], but has not yet set foo.

By the way, I wouldn't do it that way at all. Check out the dispatch_once() function, which lets you guarantee that a block is only ever executed once during your app's lifetime (assuming you have GCD on the platform you're targeting).

木格 2024-08-31 05:36:26

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

Mike Ash 还展示了使用易失性OSMemoryBarrier();的正确解决方案。

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

另请参阅 DCL 和 C++DCL 和 java 了解更多详细信息。

This is called the double checked locking "optimization". As documented everywhere this is not safe. Even if it's not defeated by a compiler optimization, it will be defeated the way memory works on modern machines, unless you use some kind of fence/barriers.

Mike Ash also shows the correct solution using volatile and OSMemoryBarrier();.

The issue is that when one thread executes foo = [[self alloc] init]; there is no guarantee that when an other thread sees foo != 0 all memory writes performed by init is visible too.

Also see DCL and C++ and DCL and java for more details.

九公里浅绿 2024-08-31 05:36:26

在您的版本中,对 !foo 的检查可能同时在多个线程上进行,允许两个线程跳转到 alloc 块,一个等待另一个线程完成在分配另一个实例之前。

In your version the check for !foo could be occurring on multiple threads at the same time, allowing two threads to jump into the alloc block, one waiting for the other to finish before allocating another instance.

蒲公英的约定 2024-08-31 05:36:26

您可以通过仅在 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;
}

You can optimize by only taking the lock if foo==nil, but after that you need to test again (within the @synchronized) to guard against race conditions.

+ (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;
}
扛刀软妹 2024-08-31 05:36:26

如果您有中央调度,最好的方法

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

Best way if you have grand cenral dispatch

+ (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];
 }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文