【问题标题】:Mutating array while reading, not enumerating读取时改变数组,而不是枚举
【发布时间】:2012-08-12 07:51:36
【问题描述】:

如果我有两个不同的线程通过 GCD 访问 NSMutableArray 并且一个只是基于可变数组创建一个新数组,而另一个线程正在从数组中删除记录,我应该认为这是一个问题吗?也就是说,我认为只是“读取”数组的副本不应该只是得到当时数组中发生的任何事情吗?我没有在任一线程中枚举数组,但它仍然崩溃。一旦我删除读取例程,它就可以正常工作。

这里是“阅读”:

  dispatch_async(saveQueue, ^{

    NSDictionary*tempstocks=[NSDictionary dictionaryWithDictionary:self.data];

它在此线程上崩溃:*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[9]'

这是另一个线程上发生的事情:

[self.data removeObjectForKey:item];

我知道你不能在枚举时进行变异,但我认为在变异时阅读是可以的,你可能不知道你得到的是哪个版本的变异对象,但我认为这不是问题,但是显然是的。也许dictionaryWithDictionary 方法正在执行一个首先看到 X 对象的操作,但在例程完成时它包含 X-Y 对象,因此它不会在运行 dictionaryWithDictionary 时立即“捕获”整个 self.data 字典而是枚举self.data,这与枚举时的突变本质上是相同的问题?

【问题讨论】:

标签: cocoa concurrency grand-central-dispatch


【解决方案1】:

我猜你可能会使用 GCD 创建三个不同的队列:一个用于保存,第二个用于其他操作,最后一个用于操作 NSMutableArray

dispatch_async(saveQueue, ^{
    dispatch_barrier_async(_queue, ^{
            NSDictionary*tempstocks=[NSDictionary dictionaryWithDictionary:self.data];
        });
});

dispatch_async(anotherQueue, ^{
    dispatch_barrier_async(_queue, ^{
            [self.data removeObjectForKey:item];
        });
});

类似于@synchronize,但使用 GCD。

更多信息:GCD Reference/dispatch_barrier_asynchttp://www.mikeash.com/pyblog/friday-qa-2011-10-14-whats-new-in-gcd.html

编辑

为了了解哪种方式更快,我进行了几次性能测试:

- (void)usingSynchronized
{
    dispatch_queue_t writeQyeue = dispatch_queue_create("com.tikhop.writeQyeue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(writeQyeue, ^{
        for(size_t i=0; i<10000; i++)
            @synchronized (arr) {
                [arr replaceObjectAtIndex:0 withObject:[NSNumber numberWithInt:1]];
                [arr replaceObjectAtIndex:0 withObject:[NSNumber numberWithInt:2]];
                [arr replaceObjectAtIndex:0 withObject:[NSNumber numberWithInt:3]];
                [arr replaceObjectAtIndex:0 withObject:[NSNumber numberWithInt:4]];
            }
    });
}

- (void)usingGCD
{
    dispatch_queue_t writeQyeue = dispatch_queue_create("com.tikhop.writeQyeue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(writeQyeue, ^{
        for(size_t i=0; i<10000; i++)
            dispatch_barrier_async(_queue, ^{
                [arr replaceObjectAtIndex:0 withObject:[NSNumber numberWithInt:5]];
                [arr replaceObjectAtIndex:0 withObject:[NSNumber numberWithInt:6]];
                [arr replaceObjectAtIndex:0 withObject:[NSNumber numberWithInt:7]];
                [arr replaceObjectAtIndex:0 withObject:[NSNumber numberWithInt:8]];
            });
    });
}

arr = [NSMutableArray arrayWithCapacity:1];
[arr addObject:@(0)];

[self usingSynchronized];
[self usingGCD];

我得到以下结果:

【讨论】:

  • 我喜欢这个。我阅读的大多数文档和建议都建议在排除 @synchronize 的情况下尽可能使用 GCD,所以这是一个很好的建议
  • 你的回答对我来说很有意义,但我承认按照这里的建议做也很容易:stackoverflow.com/a/4676307/768472 你认为这个答案是否为使用@synchronize 提供了足够的解决方案?方法?
  • 我猜这两种方法做的一样,但 GCD 更快。
  • 不错的测试!你的addObject 方法中的@(0) 是什么?另外,_queue 是否定义为并发队列?
  • 是的,_queue 是并发队列
【解决方案2】:

你不能假设 NSDictionary 上的任何操作都是线程安全的。几乎所有的人都不是。您确实需要设置一个互斥体,@synchronize 访问您的阵列或使用 gcd 串行队列进行访问。

【讨论】:

    【解决方案3】:

    dictionaryWithDictionary: 在内部枚举参数,所以你在枚举时基本上是在变异。

    此外,一般来说,如果另一个线程将以任何方式访问对象,除非您使用某种同步原语,否则您永远不应该写入该对象。

    您认为它“读取”当前所有内容的推理通常是无效的。这里有更多关于多线程固有问题的信息Usage of registers by the compiler in multithreaded program

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-12-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多