2009-12-31 11 views
35

J'ai une question sur la sécurité des threads tout en utilisant NSMutableDictionary.sécurité de thread NSMutableDictionary

Le fil conducteur est en train de lire des données à partir de NSMutableDictionary où:

  • clé est NSString
  • valeur est UIImage

Un fil asynchrone est en train d'écrire des données ci-dessus dictionnaire (en utilisant NSOperationQueue)

Comment rendre le thread de dictionnaire ci-dessus sûr? Doit-on créer la propriété NSMutableDictionaryatomic? Ou dois-je effectuer des modifications supplémentaires?

@property(retain) NSMutableDictionary *dicNamesWithPhotos;

+2

Je ne suis pas un expert en multithreading, mais je sais que le drapeau "atomic" (par défaut pour les accesseurs @ synthesize'd) ne garantit pas la sécurité des threads. J'ai quand même pensé la même chose quand je l'ai lu pour la première fois. –

Répondre

69

NSMutableDictionary n'a pas été conçu pour être la structure de données thread-safe, et marquer simplement la propriété que atomic, ne garantit pas que les opérations de données sous-jacentes sont effectivement réalisées atomiquement (de manière sûre).

Pour veiller à ce que chaque opération est effectuée de façon sécuritaire, vous devez garder chaque opération sur le dictionnaire avec une serrure:

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

Vous devriez considérer rouler vos propres NSDictionary que simplement les délégués des appels à NSMutableDictionary tout en maintenant un verrou:

@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 

S'il vous plaît noter que parce que chaque opération nécessite l 'attente de la serrure et la tenue, il est pas tout à fait évolutive, mais il pourrait être assez bon dans votre cas.

Si vous souhaitez utiliser une bibliothèque thread appropriée, vous pouvez utiliser TransactionKit library car ils ont TKMutableDictionary qui est une bibliothèque sécurisée multithread. Personnellement, je ne l'ai pas utilisé, et il semble que ce soit une bibliothèque de travail en cours, mais vous pourriez vouloir l'essayer.

+7

+1 réponse fabuleuse –

+2

Cela ressemble à une bonne méthode, mais je ne peux pas le compiler. Je reçois '' expected ')' avant 'retval_t' ''sur la ligne' - (retval_t) forward: (SEL) sel: (arglist_t) args' Des idées? –

+5

Fabuleuse réponse. Maintenant obsolète. Utilisez une file d'attente à la place. J'ai un dictionnaire sérialisé mort simple quelque part. Je devrais le poster. Le transfert de message est lent et fragile. – bbum

1

après un peu de recherche que je veux partager avec vous cet article:

Utilisation des classes de collecte en toute sécurité avec des applications multithread http://developer.apple.com/library/mac/#technotes/tn2002/tn2059.html

Il ressemble à la réponse de notnoop ne peut pas être une solution après tout. Du point de vue du filetage, ça va, mais il y a quelques subtilités critiques. Je ne vais pas poster ici une solution mais je suppose qu'il y en a une bonne dans cet article.

+1

+1 pour remarquer que le verrouillage n'est pas suffisant dans ce cas. J'ai été mordu par cette fois aussi, la '[[[dict objectForKey: key] retenir] autorelease]' "truc" est vraiment nécessaire dans un environnement multithread. – DarkDust

+4

Ce lien est maintenant cassé, et la note technique est de 2002. Vous pourriez être mieux avec https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html. –

+0

-1 Pour ce qui est presque (mais pas tout à fait, évitant ainsi de signaler,) une réponse de lien seulement. – ArtOfWarfare

1

J'ai deux options pour utiliser nsmutabledictionary.

One est:

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

deux est:

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

Je ne sais pas pourquoi certaines personnes veulent remplacer le réglage et l'obtention de mutabledictionary.

1

Même la réponse est correcte, il y a une solution élégante et différente:

- (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 copie est importante lors de l'utilisation fil des objets dangereux, avec cela, vous pouvez éviter l'erreur possible en raison de la libération involontaire de la variable . Pas besoin d'entités thread-safe.

Si plus file d'attente aimerait utiliser le NSMutableDictionary déclarer une file d'attente privée et changer le setter à:

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); 
    } 
}); 
} 

IMPORTANT!

Vous devez définir une propre file d'attente privée sans elle, le dispatch_barrier_sync est un simple dispatch_sync

Explication détaillée est dans ce marvelous blog article.