Mike Ash Singleton:放置@synchronized
我在迈克·阿什(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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
因为这样测试就会受到竞争条件的影响。两个不同的线程可能会独立测试
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
isnil
, 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 setfoo
.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).这称为双重检查锁定“优化”。正如各地记录的那样,这是不安全的。即使它没有被编译器优化击败,它也会以现代机器上内存的工作方式被击败,除非您使用某种栅栏/屏障。
Mike Ash 还展示了使用
易失性
和OSMemoryBarrier();
的正确解决方案。问题是,当一个线程执行
foo = [[self alloc] init];
时,无法保证当另一线程看到foo != 0
时所有内存写入都会执行byinit
也是可见的。另请参阅 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
andOSMemoryBarrier();
.The issue is that when one thread executes
foo = [[self alloc] init];
there is no guarantee that when an other thread seesfoo != 0
all memory writes performed byinit
is visible too.Also see DCL and C++ and DCL and java for more details.
在您的版本中,对
!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 thealloc
block, one waiting for the other to finish before allocating another instance.您可以通过仅在 foo==nil 时获取锁定来进行优化,但之后您需要再次测试(在 @synchronized 内)以防止竞争条件。
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.
如果您有中央调度,最好的方法
Best way if you have grand cenral dispatch