如何在非ARC世界中避免使用NSMutableDictionary EXC_BAD_ACCESS?

时间:2014-05-28 12:46:55

标签: objective-c multithreading memory-management dictionary exc-bad-access

根据以下代码,......

// Init a dictionary
NSMutableDictionary *dic = [NSMutableDictionary alloc] init];
[dic setObject:anObj forKey:@"key"]

...

// A : run on thread X
OBJ *obj = [dic objectForKey:@"key"];
[obj doSomething];

...

// B : run on thread Y
[dic removeObjectForKey:@"key"];

如果A和B在不同的线程上运行,X和Y,EXC_BAD_ACCESS可以发生,对吗? 因为:

  1. -[NSDictionary objectForKey:]不会保留并自动恢复返回的值。
  2. -[NSMutableDictionary removeObjectForKey:]会释放该对象。
  3. 如果首先执行[obj doSomething];

    [dic removeObjectForKey:@"key"];将崩溃。

    弹出一个问题:

      

    我们怎样才能避免这种情况?

    我的第一个想法是保留-[NSDictionary objectForKey:]的返回值,但这还不够。 为什么?由于上述两个原因,有一个持续时间,返回值仅由字典保留。如果B在该持续时间内执行,它肯定会崩溃。

    // A : run on thread X
    OBJ *obj = [[[dic objectForKey:@"key"] retain] autorelease];
    [obj doSomething];
    
    ...
    
    // B : run on thread Y
    [dic removeObjectForKey:@"key"];
    

    所以,只保留不起作用。 RIP第一个想法

    第二个想法是将它们序列化

    // A : run on thread X
    @synchronized(dic) {
      OBJ *obj = [[dic objectForKey:@"key"] retain] autorelease];
    }
    [obj doSomething];
    
    ...
    
    // B : run on thread Y
    @synchronized(dic) {
      [dic removeObjectForKey:@"key"];
    }
    

    如果我这样做,它会正常工作。

    第三个想法是过度保留字典中的所有对象并在从字典中删除或释放字典时释放它们。

    // Init a dictionary
    NSMutableDictionary *dic = [NSMutableDictionary alloc] init];
    [anObj retain];
    [dic setObject:anObj forKey:@"key"]
    
    ...
    
    // A : run on thread X
    OBJ *obj = [dic objectForKey:@"key"];
    [obj doSomething];
    
    ...
    
    // B : run on thread Y
    OBJ *obj = [dic objectForKey:@"key"];
    [dic removeObjectForKey:@"key"];
    [obj release];
    

    以下是我的问题:

    • 非ARC世界的人如何在不面对EXC_BAD_ACCESS的情况下使用NSMutableDictionary?
    • 他们总是使用单线程吗?
    • 他们总是像我上面演示的那样使用锁定或同步吗?
    • 而且,在ARC世界,这种情况会发生吗?
    • 或者我的假设都错了?

2 个答案:

答案 0 :(得分:3)

NSMutableDictionary不是线程安全的。 obj上的保留只是问题的一部分。即使您正在访问不同的元素,也无法在单独的线程上读取字典时安全地改变字典。你可以得到垃圾。它不是一个线程安全的容器。这与ARC无关。

这个问题有很多解决方案。在许多情况下,NSMutableDictionary无论如何都是错误的工具。使用小值对象可以更好地使用它。使用值对象可以通过各种方式控制线程安全。

通常最好的工具是GCD。 Jano在Multi-threaded Objective-C accessors: GCD vs locks中提供了一个很好的例子。我建议使用值对象,但如果需要,可以使用相同的技术来包装NSMutableDictionary。它允许并行读取和非阻塞写入,而不需要内核锁定。它性能高,不易实现。

我成功使用的另一种技术是切换到不可变数据结构。使用NSDictionary而不是NSMutableDictionary。有两种方法可以做到这一点:

  • 存储可变字典,但将不可变副本传递给其他线程或调用者,或
  • 存储不可变字典。当你需要改变它时,改为替换它。

对于不经常变异的合理大小的词典,这可能比你想象的要快得多,并且它使线程安全性变得更加简单。请记住,复制不可变对象几乎是免费的(通常实现为保留)。

答案 1 :(得分:0)

如果您需要从 [obj doSomething]之后的字典中删除该对象,那么这意味着您不需要并发,因此只需在同一个线程中执行此操作。

// A : run on thread X
OBJ *obj = [dic objectForKey:@"key"];
[obj doSomething];
[dic removeObjectForKey:@"key"];

否则您可以使用NSCondition对象

// Init a dictionary
NSMutableDictionary *dic = [NSMutableDictionary alloc] init];
[dic setObject:anObj forKey:@"key"]

// Init the condition object
NSCondition *myCondition = [[NSCondition alloc] init];

...

// A : run on thread X
OBJ *obj = [dic objectForKey:@"key"];
[obj doSomething];
[myCondition signal];

...

// B : run on thread Y
[myCondition wait]; // will wait until thread X calls signal
[dic removeObjectForKey:@"key"];