【问题标题】:NSMutableDictionary thread safetyNSMutableDictionary 线程安全
【发布时间】:2009-12-31 19:16:18
【问题描述】:

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

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

  • 密钥是NSString
  • 值为UIImage

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

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

我应该将NSMutableDictionary 属性设为atomic 吗?还是我需要进行任何其他更改?

@property(retain) NSMutableDictionary *dicNamesWithPhotos;

【问题讨论】:

  • 我不是多线程专家,但我知道“原子”标志(@synthesize'd 访问器的默认值)不能保证线程安全。不过,当我第一次读到它时,我确实有同样的想法。

标签: objective-c nsmutabledictionary


【解决方案1】:

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,这是一个多线程安全库。我个人没用过,看来是个正在开发中的库,你不妨试试看。

【讨论】:

  • 这看起来是个不错的方法,但我无法编译。我收到"expected ')' before 'retval_t'" 在线- (retval_t) forward: (SEL) sel : (arglist_t) args 有什么想法吗?
  • 绝妙的答案。现在已经过时了。请改用队列。我在某处有一本简单的序列化字典。我应该发布它。消息转发缓慢且脆弱。
  • 跟进 bbum 的评论。 Mike Ash 的博客上有一篇关于使用 GCD 并发队列和 NSMutableDictionary 的有用文章:mikeash.com/pyblog/friday-qa-2011-10-14-whats-new-in-gcd.html
  • @bbum 你有没有设法发布你的序列化字典的例子?
  • 使用@sychronized 来保证安全怎么样?
【解决方案2】:

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

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

不再需要NSLock 对象

【讨论】:

  • 这和@prooerty(原子)字典不一样吗?
  • 不,这意味着用一个全新的实例替换字典是原子的。不改变字典中的一个元素。
  • 非常感谢!这是一个救生员。
【解决方案3】:

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

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

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

【讨论】:

  • +1 表示在这种情况下锁定是不够的。我也被这个咬过一次,[[[dict objectForKey:key] retain] autorelease]“技巧”在多线程环境中确实是必要的。
  • 该链接现已损坏,技术说明来自 2002 年。您最好使用developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/…
  • -1 对于几乎(但不完全,因此避免标记)链接的答案。
【解决方案4】:

我有两个使用 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];
});

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

【讨论】:

    【解决方案5】:

    即使答案是正确的,也有一个优雅而不同的解决方案:

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

    【讨论】:

      猜你喜欢
      • 2012-02-17
      • 1970-01-01
      • 2022-11-21
      • 1970-01-01
      • 1970-01-01
      • 2021-07-12
      • 2011-03-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多