2009-12-31 5 views
35

Ich habe eine Frage zur Threadsicherheit bei der Verwendung NSMutableDictionary.NSMutableDictionary Thread Sicherheit

Der Haupt-Thread Daten aus NSMutableDictionary Lesen wobei:

  • Schlüssel ist NSString
  • Wert UIImage

Ein asynchroner Faden wird über Wörterbuchdaten zu schreiben (unter Verwendung von NSOperationQueue)

Wie mache ich den obigen Wörterbuch Thread sicher?

Sollte ich die NSMutableDictionary Eigenschaft atomic machen? Oder muss ich zusätzliche Änderungen vornehmen?

@property(retain) NSMutableDictionary *dicNamesWithPhotos;

+2

Ich bin kein Experte für Multithreading, aber ich weiß, dass das "atomische" Flag (der Standard für @ synthetisierte Accessoren) keine Garantien für die Threadsicherheit gibt. Ich habe das gleiche gedacht, als ich das erste Mal darüber gelesen habe. –

Antwort

69

NSMutableDictionary ist nicht auf dem Threadsichere Datenstruktur, gestaltet und einfach Markierung die Eigenschaft als atomic, stellt nicht sicher, dass die zugrundeliegenden Daten Operationen werden atomar tatsächlich durchgeführt (in einer sicheren Weise).

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

Sie sollten Ihre eigenen NSDictionary die einfach Delegierten ruft NSMutableDictionary betrachten rollen:

Um sicherzustellen, dass jeder Betrieb in einer sicheren Weise durchgeführt wird, würden Sie jede Operation auf dem Wörterbuch mit einer Sperre schützen müssen während einer Sperre hält:

@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 

Bitte beachten sie, dass, da jeder Betrieb für die Sperre warten erfordert und hält es, ist es nicht ganz skalierbar, aber es könnte in Ihrem Fall gut genug sein.

Wenn Sie eine richtige Thread-Bibliothek verwenden möchten, können Sie TransactionKit library verwenden, da sie TKMutableDictionary haben, die eine sichere Multithread-Bibliothek ist. Ich persönlich habe es nicht benutzt, und es scheint, dass es eine Work-in-Progress-Bibliothek ist, aber Sie sollten es vielleicht versuchen.

+7

+1 fabelhafte Antwort –

+2

Dies sieht wie eine gute Methode aus, aber ich kann es nicht kompilieren. Ich bekomme '' erwartet '' vor 'retval_t' "' in der Zeile '- (retval_t) vorwärts: (SEL) sel: (arglist_t) args' Irgendwelche Ideen? –

+5

Fabelhafte Antwort. Jetzt veraltet. Verwenden Sie stattdessen eine Warteschlange. Ich habe irgendwo ein einfaches serialisiertes Wörterbuch. Ich sollte es posten. Nachrichtenweiterleitung ist langsam und zerbrechlich. – bbum

1

nach ein wenig Forschung, die ich mit Ihnen in diesem Artikel teilen möchten:

Klassen Sammlung Verwendung sicher mit Multithreading-Anwendungen http://developer.apple.com/library/mac/#technotes/tn2002/tn2059.html

Es sieht aus wie die Antwort von notnoop keine Lösung nach allen. Aus Threading-Perspektive ist es in Ordnung, aber es gibt einige kritische Feinheiten. Ich werde hier keine Lösung posten, aber ich denke, dass es in diesem Artikel einen guten gibt.

+1

+1 für die Feststellung, dass die Verriegelung in diesem Fall nicht ausreicht. Ich wurde von diesem auch einmal gebissen, die '[[[dict objectForKey: key] behalten] Autorelease]' "Trick" ist wirklich notwendig in einer Multithread-Umgebung. – DarkDust

+4

Dieser Link ist jetzt defekt, und der Technote stammt aus dem Jahr 2002. Sie könnten mit https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html besser dran sein. –

+0

-1 Für was ist fast (aber nicht ganz, so vermeiden, zu markieren,) eine Verbindung nur beantworten. – ArtOfWarfare

1

Ich habe zwei Möglichkeiten, nsmutabledictionary zu verwenden.

Eine davon ist:

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

Zwei ist:

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

Ich weiß nicht, warum manche Menschen das Setzen und Empfangen von mutabledictionary überschrieben werden soll.

1

Auch die Antwort ist richtig, es ist eine elegante und andere Lösung:

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

Die Kopie ist wichtig, wenn Thread unsichere Objekte verwenden, damit könnten Sie die möglichen Fehler zu vermeiden, weil einer unbeabsichtigten Freisetzung der Variablen . Keine Notwendigkeit für Thread-sichere Entitäten.

Wenn mehr Warteschlange möchte die NSMutableDictionary verwenden, um eine private Warteschlange erklären und die Setter ändern:

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

WICHTIG!

Sie haben eine eigene private Warteschlange zu setzen, ohne es der dispatch_barrier_sync ist nur ein einfaches dispatch_sync

Ausführliche Erklärung in diesem marvelous blog article ist.