2009-12-31 18 views
35

Ho una domanda sulla sicurezza del thread durante l'utilizzo di NSMutableDictionary.Sicurezza thread NSMutableDictionary

Il filo principale sta leggendo dati da NSMutableDictionary dove:

  • chiave è NSString
  • valore è UIImage

un thread asincrono scrittura dei dati sopra dizionario (usando NSOperationQueue)

Come posso rendere sicuro il thread del dizionario sopra?

Devo fare la proprietàatomic? O devo apportare ulteriori modifiche?

@property(retain) NSMutableDictionary *dicNamesWithPhotos;

+2

Non sono esperto di multithreading, ma so che il flag "atomic" (l'impostazione predefinita per @ sintetizza gli accessors) non garantisce la sicurezza del thread. Ho pensato la stessa cosa quando l'ho letto per la prima volta, comunque. –

risposta

69

NSMutableDictionary non è progettato per essere struttura di dati thread-safe, e semplicemente segna la proprietà come atomic, non assicura che le operazioni di dati sottostanti sono eseguiti atomicamente (in modo sicuro).

per garantire che ogni operazione viene eseguita in modo sicuro, si avrebbe bisogno di custodire ogni operazione sul dizionario con una serratura:

// 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]; 

Si dovrebbe considerare a rotazione i propri NSDictionary che semplicemente delegati chiamate a NSMutableDictionary mentre si tiene una serratura:

@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 

si prega di notare che, poiché ogni operazione richiede l'attesa per il blocco e tenendolo, non è abbastanza scalabile, ma potrebbe essere abbastanza buono nel tuo caso.

Se si desidera utilizzare una libreria con filettatura corretta, è possibile utilizzare TransactionKit library in quanto hanno TKMutableDictionary che è una libreria sicura multi-thread. Personalmente non l'ho usato, e sembra che sia una libreria work in progress, ma potreste voler provarlo.

+7

+1 risposta favolosa –

+2

Questo sembra un ottimo metodo ma non riesco a farlo compilare. Sto ricevendo "" previsto ")" prima di "retval_t" "' sulla riga '- (retval_t) avanti: (SEL) sel: (arglist_t) args' Qualche idea? –

+5

Risposta favolosa. Ora obsoleto. Utilizzare invece una coda. Ho un dizionario serializzato semplice morto da qualche parte. Dovrei postarlo. L'inoltro dei messaggi è lento e fragile. – bbum

1

dopo un po 'di ricerca che voglio condividere con voi questo articolo:

Uso delle classi di raccolta in tutta sicurezza con le applicazioni multithread http://developer.apple.com/library/mac/#technotes/tn2002/tn2059.html

Sembra che la risposta di notnoop non può essere una soluzione, dopo tutto. Dal punto di vista del threading è ok, ma ci sono alcune sottigliezze critiche. Non invierò qui una soluzione, ma immagino che ce ne sia una buona in questo articolo.

+1

+1 per aver notato che il blocco non è sufficiente in questo caso. Anch'io sono stato morso da questo una volta, il '[[[dict objectForKey: key] retain] autorelease]" trucco "è davvero necessario in un ambiente multithreaded. – DarkDust

+4

Questo link è ora rotto e la technote è del 2002. Potresti star meglio con https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html. –

+0

-1 Per ciò che è quasi (ma non del tutto, evitando così di contrassegnare), una risposta solo di collegamento. – ArtOfWarfare

1

Ho due opzioni per l'utilizzo di nsmutabledictionary.

Uno è:

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

Due è:

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

non so il motivo per cui alcune persone vogliono sovrascrivere impostare e ottenere di mutabledictionary.

1

Anche la risposta è corretta, non v'è una soluzione elegante e diverso:

- (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 è importante quando si utilizza filo oggetti non sicuri, con questo si potrebbe evitare la possibile errore a causa di emissione accidentale della variabile . Non c'è bisogno di entità thread-safe.

Se più code vorrebbe utilizzare il NSMutableDictionary dichiarare una coda privata e cambiare il setter 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!

è necessario impostare una propria coda privata senza di essa il dispatch_barrier_sync è solo un semplice dispatch_sync

spiegazione dettagliata è in questo marvelous blog article.