Вопрос

Я наткнулся на это в статье Майка Эша «Уход и кормление одиночек» и был немного озадачен его комментарием:

Однако этот код довольно медленный.Взять замок довольно дорого.Еще более болезненным является тот факт, что что в подавляющем большинстве случаев Замок не имеет смысла.Замок Нужен только тогда, когда foo равен nil, что В основном это происходит только один раз.После того, как singleton, необходимость в Замок пропал, но сам замок остатки.

+(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 и С++ и DCL и Java Больше подробностей.

В вашей версии проверка на !foo может происходить в нескольких потоках одновременно, что позволяет двум потокам перейти в 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];
 }
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top