Objective c 在非ARC世界中,如何使用NSMutableDictionary避免EXC_坏访问?

Objective c 在非ARC世界中,如何使用NSMutableDictionary避免EXC_坏访问?,objective-c,multithreading,memory-management,dictionary,exc-bad-access,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 rem

根据下面的代码

// 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\u BAD\u访问
,对吗?。 因为:

  • -[NSDictionary objectForKey:][/code>不保留并自动删除返回值
  • -[NSMutableDictionary removeObjectForKey:
    不会释放该对象
  • [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"];
    
    所以,只有保留不起作用。撕毁第一个想法

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

    如果我这样做,它会工作得很好

    第三个想法是在字典中保留所有对象,并在从字典中删除或解除锁定字典时释放它们

    // 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世界的人们如何使用NSMutableDictionary而不面对
      EXC\u BAD\u访问
    • 他们总是使用单线程吗
    • 他们是否总是像我上面演示的那样使用锁定或同步
    • 而且,在ARC世界中,这种情况会发生吗
    • 还是我的假设都错了

    如果您需要在
    [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"];
    

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

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

    通常最好的工具是GCD。Jano在这方面提供了一个很好的例子。我建议使用值对象,但如果需要,可以使用相同的技术包装
    NSMutableDictionary
    。它允许并行读取和非阻塞写入,而不需要内核锁。它的性能很高,不难实现

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

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

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

    我敢肯定,在ARC world中也会遇到同样的问题。感谢您的确认,我也这么认为:D.我添加了非ARC关键字,因为我不确定ARC对我有什么作用。你的假设#2是错误的:为什么你认为objectForKey没有保留自动释放你的值?从保留计数的角度来看,整个代码看起来是线程安全的。我遗漏了什么吗?@AlessandroDiaferia自己试试看,@AlessandroDiaferia忘了告诉你这是非arc代码。首先,谢谢你的回答。GCD访问器方法,我试过一次,效果非常好,直到达到操作系统的线程限制。当僵局可能发生时。写时复制是我尝试过的另一种方法。在我存储一个不可变的字典时,将字典更改为新字典有一个关键部分。所以需要锁。即使在另一种情况下,如果删除和复制同时发生,仍然可以执行错误访问。同样,需要锁。也许你的第一个建议是基础,
    NSMutableDictionary
    是错误的工具。所以避免使用它。我会记住这一点。“GCD访问器方法,我曾经尝试过一次,在达到操作系统的线程限制之前效果很好。”这没有意义。GCD队列独立于线程。是的,交换字典仍然必须以线程安全的方式进行(设置属性也不是线程安全的)。作为旁注:不应该再有一个“非ARC世界”。非ARC代码适用的地方数量已经接近于零好几年了。我在2012年写了这篇文章,它只是变得更加真实:关于GCD,它并不独立于线程。我认为这只是给系统提供关于线程的控制。正如GCD Apple文档中所述:提交到调度队列的块在系统完全管理的线程池上执行。如果您有一个并发队列,并将大量任务分派到其中。GCD将创建一个线程来运行每个任务。MacOSX每个进程的默认线程限制仅为64。所以,达到极限并不难。关于ARC,即使在ARC中,这种EXC_坏_访问问题也可能发生,但更难。我跑了,它撞坏了;ARC不能防止错误访问异常。我只是说,今天很少有不强烈推荐ARC的情况。是的,有可能用并行请求压倒GCD,但是并行访问器模式不应该这样做,除非你从属性中读取的次数比你应该的要多。我同意使用ARC,我现在使用ARC。我问了这些关于非ARC环境beca的问题
    // 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"];