web-dev-qa-db-fra.com

Sécurité du thread de Nsmutabledictionner

J'ai une question sur la sécurité du fil en utilisant NSMutableDictionary.

Le fil principal est la lecture des données de NSMutableDictionary où:

  • clé est NSString
  • la valeur est UIImage

Un fil asynchrone rédige des données sur le dictionnaire ci-dessus (en utilisant NSOperationQueue)

Comment faire le fil de dictionnaire ci-dessus en sécurité?

Devrais-je faire la propriété NSMutableDictionaryatomic? Ou dois-je apporter des changements supplémentaires?

@property(retain) NSMutableDictionary *dicNamesWithPhotos;

39
Raj

NSMutableDictionary _ N'est pas conçu pour être une structure de données de fil-toile et simplement marquer la propriété comme atomic, ne garantit pas que les opérations de données sous-jacentes sont réellement effectuées atomiquement (de manière sûre) .

Pour que chaque opération soit effectuée de manière sûre, vous auriez besoin de 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 envisager de rouler votre propre NSDictionary qui déléguette simplement des appels à Nsmutabledictionner tout en maintenant une serrure:

@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

Veuillez noter que, parce que chaque opération nécessite d'attendre la serrure et de le retenir, ce n'est pas assez évolutif, mais cela pourrait être assez bon dans votre cas.

Si vous souhaitez utiliser une bibliothèque filetée appropriée, vous pouvez utiliser Bibliothèque transactionkit car ils ont TKMutableDictionary qui est une bibliothèque de sécurité multi-threadée. Personnellement, je ne l'ai pas utilisé et il semble que ce soit une œuvre de travail en cours de développement, mais vous voudrez peut-être essayer.

73
notnoop

J'ai deux options pour utiliser Nsmutableddictionner.

L'un 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 écraser le réglage et l'obtention de mutable.

1
gigir

De nos jours, vous iriez probablement pour @synchronized(object) à la place.

...
@synchronized(dictionary) {
    [dictionary setObject:image forKey:name];
}
...
@synchronized(dictionary) {
    [dictionary objectForKey:key];
}
...
@synchronized(dictionary) {
    [dictionary removeObjectForKey:key];
}

Pas besoin de l'objet NSLock plus

1
P.Melch

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 lorsque vous utilisez des objets de thread dangereux, avec cela, vous pouvez éviter l'erreur possible en raison de la libération inattendue de la variable. Pas besoin d'entités sécurisées de fil.

Si plus de la file d'attente souhaiterait utiliser le NSMutabledictionner déclarer une file d'attente privée et modifier le Setter sur:

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 le Dispatch_Barrier_sync est juste un simple Dispatch_Sync

Une explication détaillée est dans cet ensemble Article de blog merveilleux .

1
BootMaker

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

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

Il ressemble à la réponse de Notnoop peut ne pas être une solution après tout. De la perspective de filetage, c'est bon, mais il y a quelques subtilités critiques. Je ne posterai pas ici une solution mais je suppose qu'il y a un bon dans cet article.

1
Adrian