2009-12-31 12 views
35

Tengo una pregunta sobre seguridad de subprocesos al utilizar NSMutableDictionary.NSMutableDictionary thread safety

El hilo principal está leyendo datos NSMutableDictionary donde:

  • clave es NSString
  • valor es UIImage

Un hilo asíncrono está escribiendo los datos anteriormente diccionario (usando NSOperationQueue)

¿Cómo hago que el hilo de diccionario anterior sea seguro?

¿Debo hacer la propiedad NSMutableDictionaryatomic? ¿O necesito hacer algún cambio adicional?

@property(retain) NSMutableDictionary *dicNamesWithPhotos;

+2

No soy un experto en multihilo, pero sí sé que el indicador "atómico" (el predeterminado para @ accessors sintetizados) no garantiza la seguridad del hilo. Sin embargo, pensé lo mismo cuando leí por primera vez al respecto. –

Respuesta

69

NSMutableDictionary no está diseñado para ser estructura de datos thread-safe, y simplemente marcando la propiedad como atomic, no asegura que las operaciones de datos subyacentes están realmente lleva a cabo atómicamente (de una manera segura).

Para asegurarse de que cada operación se realiza de una manera segura, lo que se necesita para proteger cada operación en el diccionario con una cerradura:

// in initialization 
self.dictionary = [[NSMutableDictionary alloc] init]; 
// create a lock object for the dictionary 
self.dictionary_lock = [[NSLock alloc] init]; 


// at every access or modification: 
[object.dictionary_lock lock]; 
[object.dictionary setObject:image forKey:name]; 
[object.dictionary_lock unlock]; 

usted debe considerar rodar sus propias NSDictionary que simplemente delegados llamadas a NSMutableDictionary mientras que mantiene un bloqueo:

@interface SafeMutableDictionary : NSMutableDictionary 
{ 
    NSLock *lock; 
    NSMutableDictionary *underlyingDictionary; 
} 

@end 

@implementation SafeMutableDictionary 

- (id)init 
{ 
    if (self = [super init]) { 
     lock = [[NSLock alloc] init]; 
     underlyingDictionary = [[NSMutableDictionary alloc] init]; 
    } 
    return self; 
} 

- (void) dealloc 
{ 
    [lock_ release]; 
    [underlyingDictionary release]; 
    [super dealloc]; 
} 

// forward all the calls with the lock held 
- (retval_t) forward: (SEL) sel : (arglist_t) args 
{ 
    [lock lock]; 
    @try { 
     return [underlyingDictionary performv:sel : args]; 
    } 
    @finally { 
     [lock unlock]; 
    } 
} 

@end 

Tenga en cuenta que debido a que cada operación requiere que espera el bloqueo y se mantiene, no es muy escalable, pero podría ser lo suficientemente bueno en su caso.

Si desea utilizar una biblioteca con hilos adecuada, puede usar TransactionKit library ya que tienen TKMutableDictionary que es una biblioteca de seguridad con múltiples subprocesos. Personalmente no lo he usado, y parece que es una biblioteca en progreso, pero es posible que desee probarlo.

+7

+1 fabuloso respuesta –

+2

Esto parece un gran método, pero no logro compilarlo. Me sale '' esperado ')' antes 'retval_t' "' en la línea '- (retval_t) forward: (SEL) sel: (arglist_t) args' ¿Alguna idea? –

+5

Respuesta fabuloso. Ahora obsoleto. Use una cola en su lugar. Tengo un diccionario serializado muerto en alguna parte. Debería publicarlo. El reenvío de mensajes es lento y frágil. – bbum

1

después de un poco de la investigación que quiero compartir con ustedes este artículo:

Usando clases de colección segura con las aplicaciones multitarea http://developer.apple.com/library/mac/#technotes/tn2002/tn2059.html

Parece que la respuesta de notnoop puede no ser una solución después de todo. Desde la perspectiva de enhebrar, está bien, pero hay algunas sutilezas críticas. No publicaré aquí una solución, pero creo que hay una buena en este artículo.

+1

+1 para notar que el bloqueo no es suficiente en este caso. Fui mordido por esta vez también, el '[[[dict objectForKey: key] retener] autorelease]' "truco" realmente es necesario en un entorno multiproceso. – DarkDust

+4

Ese enlace ahora está roto, y la nota técnica es de 2002. Puede que esté mejor con https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html. –

+0

-1 Por lo que es casi (pero no del todo, evitando así marcar,) una respuesta de solo un enlace. – ArtOfWarfare

1

Tengo dos opciones para usar nsmutabledictionary.

Una de ellas es:

NSLock* lock = [[NSLock alloc] init]; 
[lock lock]; 
[object.dictionary setObject:image forKey:name]; 
[lock unlock]; 

Dos es:

//Let's assume var image, name are setup properly 
dispatch_async(dispatch_get_main_queue(), 
^{ 
     [object.dictionary setObject:image forKey:name]; 
}); 

No sé por qué algunas personas quieren sobreescribir establecer y obtener de mutabledictionary.

1

Incluso la respuesta es correcta, no es una solución elegante y diferente:

- (id)init { 
self = [super init]; 
if (self != nil) { 
    NSString *label = [NSString stringWithFormat:@"%@.isolation.%p", [self class], self]; 
    self.isolationQueue = dispatch_queue_create([label UTF8String], NULL); 

    label = [NSString stringWithFormat:@"%@.work.%p", [self class], self]; 
    self.workQueue = dispatch_queue_create([label UTF8String], NULL); 
} 
return self; 
} 
//Setter, write into NSMutableDictionary 
- (void)setCount:(NSUInteger)count forKey:(NSString *)key { 
key = [key copy]; 
dispatch_async(self.isolationQueue, ^(){ 
    if (count == 0) { 
     [self.counts removeObjectForKey:key]; 
    } else { 
     self.counts[key] = @(count); 
    } 
}); 
} 
//Getter, read from NSMutableDictionary 
- (NSUInteger)countForKey:(NSString *)key { 
__block NSUInteger count; 
dispatch_sync(self.isolationQueue, ^(){ 
    NSNumber *n = self.counts[key]; 
    count = [n unsignedIntegerValue]; 
}); 
return count; 
} 

La copia es importante cuando se utilizan los objetos peligrosos de rosca, con esto se podría evitar el posible error debido a la liberación involuntaria de la variable . No es necesario contar con entidades seguras para hilos.

Si hay más cola le gustaría usar el NSMutableDictionary declarar una cola privada y cambiar el colocador a:

self.isolationQueue = dispatch_queue_create([label UTF8String], DISPATCH_QUEUE_CONCURRENT); 

- (void)setCount:(NSUInteger)count forKey:(NSString *)key { 
key = [key copy]; 
dispatch_barrier_async(self.isolationQueue, ^(){ 
    if (count == 0) { 
     [self.counts removeObjectForKey:key]; 
    } else { 
     self.counts[key] = @(count); 
    } 
}); 
} 

IMPORTANTE!

usted tiene que establecer una propia cola privada sin ella el dispatch_barrier_sync es sólo un simple dispatch_sync

explicación detallada se encuentra en este marvelous blog article.