I have a question on thread safety while using NSMutableDictionary
.
The main thread is reading data from NSMutableDictionary
where:
NSString
UIImage
An asynchronous thread is writing data to above dictionary (using NSOperationQueue
)
How do I make the above dictionary thread safe?
Should I make the NSMutableDictionary
property atomic
? Or do I need to make any additional changes?
@property(retain) NSMutableDictionary *dicNamesWithPhotos;
Atomic is the default behaviour for a property.An atomic property adds a level of thread safety when getting or setting values. That is, the getter and setter for the property will always be fully completed regardless of what other threads are doing.
Thread safety becomes a concern if there is at least a single entry point which can be accessed by multiple threads. If a piece of code is accessed by multiple threads and is calling other method/class/etc., then all this code tree becomes vulnerable.
In general, immutable classes like NSArray are thread-safe, while their mutable variants like NSMutableArray are not. In fact, it's fine to use them from different threads, as long as access is serialized within a queue.
Thread safety is the avoidance of data races—situations in which data are set to either correct or incorrect values, depending upon the order in which multiple threads access and modify the data. When no sharing is intended, give each thread a private copy of the data.
NSMutableDictionary
isn't designed to be thread-safe data structure, and simply marking the property as atomic
, doesn't ensure that the underlying data operations are actually performed atomically (in a safe manner).
To ensure that each operation is done in a safe manner, you would need to guard each operation on the dictionary with a lock:
// 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];
You should consider rolling your own NSDictionary
that simply delegates calls to NSMutableDictionary while holding a 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
Please note that because each operation requires waiting for the lock and holding it, it's not quite scalable, but it might be good enough in your case.
If you want to use a proper threaded library, you can use TransactionKit library as they have TKMutableDictionary
which is a multi-threaded safe library. I personally haven't used it, and it seems that it's a work in progress library, but you might want to give it a try.
Nowadays you'd probably go for @synchronized(object)
instead.
... @synchronized(dictionary) { [dictionary setObject:image forKey:name]; } ... @synchronized(dictionary) { [dictionary objectForKey:key]; } ... @synchronized(dictionary) { [dictionary removeObjectForKey:key]; }
No need for the NSLock
object any more
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With