使用NSMutableDictionary
时,我对线程安全性有疑问。
主线程是从NSMutableDictionary
读取数据,其中:
NSString
UIImage
异步线程正在将数据写入上面的字典(使用NSOperationQueue
)
如何使上述字典线程安全?
我应该制作NSMutableDictionary
属性atomic
吗?或者我是否需要进行任何其他更改?
@property(retain) NSMutableDictionary *dicNamesWithPhotos;
答案 0 :(得分:72)
NSMutableDictionary
不是设计为线程安全的数据结构,只是将属性标记为atomic
,并不能确保基础数据操作实际上是以原子方式执行的(以安全的方式) )。
为了确保每个操作都以安全的方式完成,您需要使用锁来保护字典上的每个操作:
// 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];
你应该考虑滚动你自己的NSDictionary
,它只是在持有锁的情况下将调用委托给NSMutableDictionary:
@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
请注意,因为每个操作都需要等待锁定并保持锁定,所以它的扩展性不是很高,但在您的情况下它可能已经足够了。
如果要使用正确的线程库,可以使用TransactionKit library,因为它们是TKMutableDictionary
,它是一个多线程安全库。我个人还没有使用它,它似乎是一个正在进行中的工作库,但你可能想尝试一下。
答案 1 :(得分:1)
使用多线程应用程序安全地使用集合类 http://developer.apple.com/library/mac/#technotes/tn2002/tn2059.html
看起来notnoop的答案可能根本不是解决方案。从线程角度来看,这是可以的,但有一些关键的微妙之处。我不会在这里发布一个解决方案,但我想在本文中有一个很好的解决方案。
答案 2 :(得分:1)
我有两个使用nsmutabledictionary的选项。
一个是:
NSLock* lock = [[NSLock alloc] init];
[lock lock];
[object.dictionary setObject:image forKey:name];
[lock unlock];
两个是:
//Let's assume var image, name are setup properly
dispatch_async(dispatch_get_main_queue(),
^{
[object.dictionary setObject:image forKey:name];
});
我不知道为什么有些人想要覆盖设置和获取mutabledictionary。
答案 3 :(得分:1)
即使答案是正确的,也有一个优雅而不同的解决方案:
- (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;
}
使用线程不安全对象时,副本很重要,这可以避免由于意外释放变量而导致的错误。不需要线程安全实体。
如果有更多队列想要使用NSMutableDictionary声明一个私有队列并将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);
}
});
}
重要!强>
你必须在没有它的情况下设置一个自己的私人队列 dispatch_barrier_sync 只是一个简单的 dispatch_sync
详细解释见marvelous blog article。
答案 4 :(得分:1)
如今,您可能会去expect(authService.authenticate.calls.mostRecent().args[0]).toEqual(authObj);
。
@synchronized(object)
不再需要...
@synchronized(dictionary) {
[dictionary setObject:image forKey:name];
}
...
@synchronized(dictionary) {
[dictionary objectForKey:key];
}
...
@synchronized(dictionary) {
[dictionary removeObjectForKey:key];
}
对象