マイク・アッシュ・シングルトン:@synchronized を配置する
-
19-09-2019 - |
質問
私はマイク・アッシュの「シングルトンのケアと給餌」でこれを見つけましたが、彼のコメントに少し当惑しました。
ただし、このコードは少し遅いです。ロックの取得には多少のコストがかかります。より痛みを伴うのは、大部分の時間であるロックが無意味であるという事実です。ロックは、fooがnilである場合にのみ必要です。これは基本的に1回しか発生しません。シングルトンが初期化された後、ロックの必要性はなくなりますが、ロック自体は残ります。
+(id)sharedFoo {
static Foo *foo = nil;
@synchronized([Foo class]) {
if(!foo) foo = [[self alloc] init];
}
return foo;
}
私の質問は、これには間違いなく正当な理由がありますが、foo が nil の場合にロックを制限するように (以下を参照) 書けないのはなぜですか?
+(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();
.
問題は、1 つのスレッドが実行されるとき、 foo = [[self alloc] init];
他のスレッドがそれを見たとき、それが起こるという保証はありません foo != 0
によって実行されるすべてのメモリ書き込み init
も見えます。
こちらもご覧ください DCL と C++ そして DCL と Java 詳細については。
あなたのバージョンでは!foo
のチェックは、同時に複数のスレッドで発生した2つのスレッドがalloc
ブロック、他の別のインスタンスを割り当てる前に終了するのを待っている一つにジャンプできるようにすることができます。
の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;
}
あなたはグランドセントラル派遣を持っている場合、最良の方法
+ (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];
}