NSMutableDictionary线程安全

时间:2009-12-31 19:16:18

标签: objective-c nsmutabledictionary

使用NSMutableDictionary时,我对线程安全性有疑问。

主线程是从NSMutableDictionary读取数据,其中:

  • 键是NSString
  • 值为UIImage

异步线程正在将数据写入上面的字典(使用NSOperationQueue

如何使上述字典线程安全?

我应该制作NSMutableDictionary属性atomic吗?或者我是否需要进行任何其他更改?

@property(retain) NSMutableDictionary *dicNamesWithPhotos;

5 个答案:

答案 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]; } 对象