44

我在使用NSMutableDictionary.

主线程正在从NSMutableDictionary以下位置读取数据:

  • 关键是NSString
  • 值为UIImage

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

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

我应该做NSMutableDictionary财产atomic吗?还是我需要进行任何其他更改?

@property(retain) NSMutableDictionary *dicNamesWithPhotos;

4

5 回答 5

77

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 库,因为它们TKMutableDictionary是一个多线程安全库。我个人没有使用过它,它似乎是一个正在进行的库,但您可能想尝试一下。

于 2009-12-31T19:52:22.680 回答
6

现在你可能会选择@synchronized(object)

...
@synchronized(dictionary) {
    [dictionary setObject:image forKey:name];
}
...
@synchronized(dictionary) {
    [dictionary objectForKey:key];
}
...
@synchronized(dictionary) {
    [dictionary removeObjectForKey:key];
}

不再需要NSLock对象

于 2018-08-02T09:04:01.587 回答
2

经过一些研究,我想与您分享这篇文章:

在多线程应用程序中安全地使用集合类 http://developer.apple.com/library/mac/#technotes/tn2002/tn2059.html

看起来notnoop的答案可能不是一个解决方案。从线程的角度来看,这是可以的,但有一些关键的微妙之处。我不会在这里发布解决方案,但我想这篇文章中有一个很好的解决方案。

于 2011-05-06T19:52:49.123 回答
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];
});

我不知道为什么有些人想覆盖可变字典的设置和获取。

于 2016-07-14T09:14:14.167 回答
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 声明一个私有队列并将设置器更改为:

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

详细的解释在这篇精彩的博客文章中

于 2016-08-05T13:57:16.437 回答