2009-12-31 18 views
35

Mam pytanie dotyczące bezpieczeństwa wątku podczas korzystania z NSMutableDictionary.Bezpieczeństwo wątków NSMutableDictionary

Główny wątek jest odczyt danych z NSMutableDictionary gdzie:

  • kluczem jest NSString
  • wartość UIImage

asynchroniczny wątek zapisuje dane powyżej słowniku (używając NSOperationQueue)

Jak ustawić powyższy słownik jako bezpieczny dla wątków?

Czy powinienem wykonać właściwość NSMutableDictionary nieruchomości atomic? Czy muszę wprowadzić dodatkowe zmiany?

@property(retain) NSMutableDictionary *dicNamesWithPhotos;

+2

Nie jestem ekspertem od wielowątkowości, ale wiem, że flaga "atomowa" (domyślna dla @ synthesize'd accessorów) nie gwarantuje bezpieczeństwa wątku. Myślałem o tym samym, kiedy po raz pierwszy przeczytałem o tym. –

Odpowiedz

69

NSMutableDictionary nie jest przeznaczony do struktury danych wątku bezpieczny, a po prostu oznaczenie nieruchomości jako atomic, nie gwarantuje, że podstawowe operacje na danych są faktycznie wykonywane atomowo (w bezpieczny sposób).

Aby upewnić się, że każda operacja jest wykonywana w sposób bezpieczny, trzeba by pilnować każdej operacji na słowniku z zamkiem:

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

Należy rozważyć toczenia własne NSDictionary które po prostu delegatów wzywa do NSMutableDictionary trzymając lock:

@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 

Należy pamiętać, że ponieważ każda operacja wymaga oczekiwania na blokady i przytrzymując go, to nie dość skalowalne, ale może to być wystarczająco dobre w Twoim przypadku.

Jeśli chcesz użyć odpowiedniej biblioteki z gwintami, możesz użyć TransactionKit library, ponieważ mają one TKMutableDictionary, która jest wielowątkową bezpieczną biblioteką. Osobiście go nie używałem i wygląda na to, że jest to biblioteka postępu, ale możesz spróbować.

+7

+1 wspaniała odpowiedź –

+2

To wygląda na świetną metodę, ale nie mogę jej skompilować. Otrzymuję '" oczekiwano ") 'before' retval_t '" 'na linii' - (retval_t) forward: (SEL) sel: (arglist_t) args' Jakieś pomysły? –

+5

Wspaniała odpowiedź. Teraz przestarzałe. Zamiast tego użyj kolejki. Mam gdzieś martwy prosty serializowany słownik. Powinienem to opublikować. Przekazywanie wiadomości jest powolne i kruche. – bbum

1

po trochę badań chcę się z wami podzielić ten artykuł:

Korzystanie z klas kolekcji bezpiecznie w aplikacjach wielowątkowych http://developer.apple.com/library/mac/#technotes/tn2002/tn2059.html

Wygląda na to odpowiedź notnoop nie może być rozwiązanie, mimo wszystko. Z perspektywy wątków jest ok, ale są pewne krytyczne subtelności. Nie zamieszczę tutaj rozwiązania, ale myślę, że jest dobry w tym artykule.

+1

+1 za zauważenie, że blokowanie w tym przypadku nie wystarcza. Również mnie to ugryzło, '[[[Dict objectForKey: key] retain] autorelease]' "trick" jest naprawdę potrzebny w środowisku wielowątkowym. – DarkDust

+4

To łącze jest teraz uszkodzone, a notatka techniczna pochodzi z 2002 roku. Lepiej być na stronie https://developer.apple.com/library/mac/#documentation/Cocoa/Conoa/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html. –

+0

-1 Za to, co prawie (ale nie całkiem, a więc unikanie flagowania), odpowiada tylko na link. – ArtOfWarfare

1

Mam dwie opcje użycia nsmutabledictionary.

Jednym z nich jest:

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

Dwa to:

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

Nie wiem dlaczego niektórzy ludzie chcą zastąpić ustawianie i pobieranie z mutabledictionary.

1

Nawet odpowiedź jest poprawna, jest elegancki i inne rozwiązanie:

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

Kopia jest ważne przy użyciu nici niebezpiecznych obiektów, z tego można uniknąć ewentualnego błędu z powodu niezamierzonego uwolnienia zmiennej . Nie ma potrzeby tworzenia wątków bezpiecznych.

Jeśli więcej kolejka chciałby użyć NSMutableDictionary zadeklarować prywatną kolejkę i zmienić setter do:

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

WAŻNE!

Musisz ustawić własną kolejkę bez niej dispatch_barrier_sync tylko proste dispatch_sync

Szczegółowe wyjaśnienie jest w tym marvelous blog article.

Powiązane problemy