【问题标题】:How can I solve "Collection was mutated while being enumerated", @synrchonized, mutableCopy, or something else?如何解决“枚举时集合发生了变异”、@synrchonized、mutableCopy 或其他问题?
【发布时间】:2016-06-05 16:08:35
【问题描述】:

在 Crashlytics 中,我看到用户很少遇到的崩溃。有问题的代码看起来像这样......

- (void)updateIsAnsweredField:(NSArray *)moduleItemsList
{
    if (moduleItemsList && self.answeredItems && self.answeredItems.count > 0) {
        for (ModuleItem * item in moduleItemsList) { // "Collection was mutated while being enumerated"
            if ([item isKindOfClass:[ModuleItem class]] && [item shouldCheckIfAnswered]) {
                item.answered = [self isAnsweredItem:item.moduleID];
            }
        }
    }
}

Crashlytics给出的错误可以在上面代码sn-p的注释中看到。

我认为有几种方法可以解决这个问题。

1) 将函数内的所有内容包装在 @synchronized(moduleItemsList) {} 中。这是解决问题的理想方法吗?我听说@synchronized 非常慢,尽可能避免使用它。

2) 创建一个副本,如 NSMutableArray *copyModuleItemsList = [moduleItemsList mutableCopy];。然后列举。这能解决问题吗?我认为它会解决这个特定问题,但会有另一个问题,不是吗?那就是...最后,当我们将副本分配回原来的 moduleItemsList = copyModuleItemsList; 时,moduleItemsList 可能同时在不同的线程上发生了变化。

3) 将传入的:(NSArray *)moduleItemsList 跟踪到将其作为属性持有的任何人。然后覆盖getter以使用dispatch_sync,并覆盖setter以使用dispatch_barrier_async。但是,不能保证原始数组是可以覆盖其 getter 和 setter 的任何类的属性。实际上,这些都没有任何意义,因为我们不会专门更改该数组吗?

我有点困惑。任何人都可以在这件事上提供帮助吗? #1 是我想要的选项吗?

编辑:添加更多代码

[item shouldCheckIFAnswered]:

这只是检查 ModuleItem 类中存在的 @property 值。 if self.moduleType == ModuleTypeSuchAndSuch

isAnsweredItem::

- (BOOL)isAnsweredItem:(NSString *)moduleID
{
    if (!self.answeredItems) {
        return NO;
    }

    return [self.answeredItems containsObject:moduleID];
}

【问题讨论】:

  • 第 1 项可能不会修复它,因为数组的修饰符在同步边界之外(同步仅在修改的每个人都同步时才有效)。您不需要可变副本;副本本身只需要是静态副本(您自己不会对其进行变异),即[moduleItemsList copy]。不过,我不明白将其复制回来的原因,因为它是一个对象数组,更改对象也会影响其他容器中的对象——这只是它们的浅拷贝。
  • 仅添加copy 并不能解决问题——在复制过程中moduleItemsList 仍有可能在另一个线程中发生变异。这也会导致崩溃。
  • @BorisVerebsky 你会怎么解决这个问题?
  • 你在 shouldCheckIfAnswered 和 isAnsweredItem 方法中做了什么?你能提供这两种方法的代码吗
  • @Arun 添加到原始帖子中。

标签: objective-c multithreading grand-central-dispatch synchronized


【解决方案1】:

从您的帖子中,听起来moduleItemsList 正在另一个线程中进行修改。解决此问题的“正确”方法将取决于另一个线程中的状态与该线程中的状态之间的所需关系。

如果您在 both 此代码中以及在另一个线程中修改集合的代码中使用 @synchronized(moduleItemsList),那么当此代码运行时,它将始终具有“最新"moduleItemsList 的视图。

如果您将moduleItemsList 复制到另一个对象中,那么当此代码运行时,它可能会在不再位于moduleItemsList 中的项目上设置answered 值,或者可能无法设置answered标记最近添加到 moduleItemsList 的项目。

一般来说,@synchronized 版本是获得“正确”行为的更简单方法。如果您确定两个线程可能不同意 moduleItemsList 的当前内容并不重要,您只想使用复制。

我听说@synchronized 很慢,尽可能避免使用它。

总的来说,这是糟糕的建议。 @synchronized 与确保线程之间的一致状态并提供重入锁一样慢。你不想随便乱扔@synchronized,不管怎样,但它是在线程之间同步数据访问的一个很好的解决方案——毕竟,这就是它的目的。

【讨论】:

  • @synchronized 花费的时间不到一微秒。另一方面,崩溃...
  • 完全正确,@gnasher729。我的意思是,我明白了——我在一个有数百个线程的应用程序上工作,过多的锁定是一个很难追踪的性能问题(因为影响分散在一堆线程上),但是人们太急于尝试在多线程代码中“聪明”,以避免锁定的“开销”。除非您正在编写真正高性能的代码,否则请使用最简单的同步方式。
  • @MarkBessey 谢谢。我应该澄清“我听说同步非常慢,并尽可能避免它。”评论。我听说有“不正确的方法,正确但不高效的方式(同步),以及更高效和更简单的方式(串行队列)。”
猜你喜欢
  • 2014-02-15
  • 2017-11-22
  • 2013-01-29
  • 2012-07-30
  • 2011-03-27
  • 2014-07-04
  • 2023-03-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多