Pregunta

Me encontré con esto en el "cuidado y alimentación de los hijos únicos" Mike Ash y estaba un poco Puzzeled por su comentario:

  

Este código es un poco lento, sin embargo.   Tomando una cerradura es un poco caro.   Por lo que es más doloroso es el hecho   que la gran mayoría de las veces,   la cerradura no tiene sentido. El bloqueo es   Sólo es necesario si foo es nula, lo cual   básicamente sólo ocurre una vez. Después de la   Singleton se inicializa, la necesidad de   el bloqueo se ha ido, pero la cerradura en sí   permanece.

+(id)sharedFoo {
    static Foo *foo = nil;
    @synchronized([Foo class]) {
        if(!foo) foo = [[self alloc] init];
    }
    return foo;
}

Mi pregunta es, y no hay duda de una buena razón para esto, pero ¿por qué no se puede escribir (ver más abajo) para limitar la cerradura para cuando foo es nula?

+(id)sharedFoo {
    static Foo *foo = nil;
    if(!foo) {
        @synchronized([Foo class]) {
            foo = [[self alloc] init];
        }
    }
    return foo;
}

aplausos Gary

¿Fue útil?

Solución

Porque entonces la prueba está sujeta a una condición de carrera. Dos hilos diferentes pueden probar de forma independiente que foo es nil, y luego (secuencialmente) crean instancias separadas. Esto puede suceder en su versión modificada cuando un hilo realiza la prueba, mientras que el otro está todavía dentro +[Foo alloc] o -[Foo init], pero aún no se ha establecido foo.

Por cierto, yo no lo haría de esa manera en absoluto. Echa un vistazo a la función dispatch_once(), que le permite garantizar que un bloque es solamente siempre ejecuta una vez durante la vida de su aplicación (suponiendo que tiene GCD de la plataforma que está apuntando).

Otros consejos

Esto se llama el doble comprobación de bloqueo "optimización" . Como se documenta en todas partes esto no es seguro. Incluso si no es derrotado por una optimización del compilador, que se desactivará el modo en que funciona la memoria en las máquinas modernas, a menos que utilice algún tipo de valla / barreras.

Mike Ash también muestra la solución correcta usando volatile y OSMemoryBarrier();.

El problema es que cuando se ejecuta un hilo foo = [[self alloc] init]; no hay garantía de que cuando un otro hilo ve foo != 0 todas las escrituras en memoria realizada por init es visible también.

También vea DCL y C ++ y DCL y java para más detalles.

En la versión de la comprobación de !foo podría estar ocurriendo en varios subprocesos al mismo tiempo, permitiendo dos hilos para saltar en el bloque alloc, uno esperando que el otro para terminar antes de asignar otra instancia.

Puede optimizar tomando solamente el bloqueo si foo == nil, pero después de que usted necesita para poner a prueba una vez más (dentro del @synchronized) para evitar las condiciones de carrera.

+ (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;
}

La mejor manera si usted tiene gran despacho central

+ (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];
 }
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top