7

私はマイク・アッシュの「シングルトンのケアと給餌」でこれに出くわし、彼のコメントに少し戸惑いました:

ただし、このコードは少し遅いです。ロックを取得するには、多少のコストがかかります。それをより苦痛にしているのは、ほとんどの場合、ロックが無意味であるという事実です。ロックが必要になるのは、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;
}

乾杯ゲイリー

4

5 に答える 5

18

その場合、テストは競合状態の影響を受けるためです。2 つの異なるスレッドが を個別にテストしfoonil(順次) 個別のインスタンスを作成する場合があります。これは、1 つのスレッドがテストを実行し、もう 1 つのスレッドがまだ+[Foo alloc]or内-[Foo init]にあり、まだ設定されていない場合に、変更されたバージョンで発生する可能性がありますfoo

ちなみに私は絶対にそんなことはしません。dispatch_once()アプリの有効期間中にブロックが一度だけ実行されることを保証できる関数を確認してください(ターゲットにしているプラ​​ットフォームに GCD があると仮定します)。

于 2010-03-10T17:01:11.497 に答える
7

これは、二重チェック ロックの「最適化」と呼ばれます。どこでも文書化されているように、これは安全ではありません。コンパイラの最適化によって無効にされなくても、ある種のフェンス/バリアを使用しない限り、最新のマシンでメモリが機能する方法では無効になります。

Mike Ash はvolatile、とを使用した正しい解も示していますOSMemoryBarrier();

問題は、1 つのスレッドが実行foo = [[self alloc] init];されたときに、別のスレッドがfoo != 0実行したすべてのメモリ書き込みを確認したときに、それinitが表示されるという保証がないことです。

詳細については、 DCL と C++およびDCL と Javaも参照してください。

于 2010-03-11T08:34:44.373 に答える
1

お使いのバージョンでは!foo、複数のスレッドで同時にチェックが行われる可能性があり、2 つのスレッドがallocブロックにジャンプし、一方が他方のスレッドの終了を待ってから別のインスタンスを割り当てることができます。

于 2010-03-10T17:04:06.830 に答える
1

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;
}
于 2010-03-10T17:04:28.777 に答える