迈克·阿什·辛格尔顿:放置@synchronized
-
19-09-2019 - |
题
我在迈克·阿什(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;
}
干杯加里
解决方案
由于则测试是受争用条件。两个不同的线程可能会独立地测试foo
是nil
,且然后(序贯)创建单独的实例。这可以在你的修改版本发生当一个线程执行测试,而其他还在里面+[Foo alloc]
或-[Foo init]
,但尚未设定foo
。
顺便说一句,我不会那样做的。退房dispatch_once()
功能,它可以让你保证你的应用程序的生命周期内的块只会被执行一次(假设你有GCD您指定的平台)。
其他提示
这被称为 双重检查锁定“优化”. 。正如各地记录的那样,这是不安全的。即使它没有被编译器优化击败,它也会以现代机器上内存的工作方式被击败,除非您使用某种栅栏/屏障。
迈克·阿什还展示了 正确的解决方案使用 volatile
和 OSMemoryBarrier();
.
问题是当一个线程执行时 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];
}