15

我的 iPhone 客户端大量参与异步请求,很多时候不断修改字典或数组的静态集合。因此,我经常看到较大的数据结构需要更长时间才能从服务器检索并出现以下错误:

*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <NSCFArray: 0x3777c0> was mutated while being enumerated.'

这通常意味着对服务器的两个请求会返回试图修改同一个集合的数据。我正在寻找的是关于如何正确构建我的代码以避免这种有害错误的教程/示例/理解。我确实相信正确的答案是互斥锁,但我还没有亲自使用过它们。

这是使用 NSURLConnection 发出异步 HTTP 请求,然后在请求完成后使用 NSNotification Center 作为委托方式的结果。当触发改变相同集合集的请求时,我们会遇到这些冲突。

4

6 回答 6

28

有几种方法可以做到这一点。在您的情况下,最简单的可能是使用 @synchronized 指令。这将允许您使用任意对象作为锁动态创建互斥锁。

@synchronized(sStaticData) {
  // Do something with sStaticData
}

另一种方法是使用 NSLock 类。创建您要使用的锁,然后在获取互斥锁时您将有更多的灵活性(关于如果锁不可用时的阻塞等)。

NSLock *lock = [[NSLock alloc] init];
// ... later ...
[lock lock];
// Do something with shared data
[lock unlock];
// Much later
[lock release], lock = nil;

如果您决定采用这些方法中的任何一种,则有必要为读取和写入获取锁,因为您使用 NSMutableArray/Set/whatever 作为数据存储。正如您所见,NSFastEnumeration 禁止对被枚举的对象进行突变。

但我认为这里的另一个问题是多线程环境中数据结构的选择。是否有必要从多个线程访问您的字典/数组?或者后台线程是否可以合并他们接收到的数据,然后将其传递给主线程,主线程将是唯一允许访问数据的线程?

于 2009-02-16T19:39:53.387 回答
15

如果有可能同时从两个线程访问任何数据(包括类),您必须采取措施保持这些同步。

幸运的是,Objective-C 使用 synchronized 关键字可以轻松地做到这一点。此关键字将任何 Objective-C 对象作为参数。在同步部分中指定相同对象的任何其他线程将暂停,直到第一个完成。

-(void) doSomethingWith:(NSArray*)someArray
 {    
    // the synchronized keyword prevents two threads ever using the same variable
    @synchronized(someArray)
    {
       // modify array
    }
 }

如果您需要保护的不仅仅是一个变量,您应该考虑使用表示对该数据集的访问的信号量。

// Get the semaphore.
id groupSemaphore = [Group semaphore];

@synchronized(groupSemaphore) 
{
    // Critical group code.
}
于 2009-02-16T19:43:34.673 回答
0

为了响应 sStaticData 和 NSLock 的答案(评论限制为 600 个字符),您是否需要非常小心地以线程安全的方式创建 sStaticData 和 NSLock 对象(以避免出现多个锁的不太可能的情况由不同的线程创建)?

我认为有两种解决方法:

1)您可以要求在一天开始时在单个根线程中创建这些对象。

2) 定义一个在一天开始时自动创建的静态对象用作锁,例如可以内联创建静态 NSString:

static NSString *sMyLock1 = @"Lock1";

那么我认为你可以安全地使用

@synchronized(sMyLock1) 
{
  // Stuff
}

否则,我认为您将始终以线程安全的方式创建锁而陷入“鸡和蛋”的境地?

当然,您不太可能遇到任何这些问题,因为大多数 iPhone 应用程序都在单线程中运行。

我不知道之前的 [Group semaphore] 建议,这也可能是一个解决方案。

于 2009-08-25T16:07:22.303 回答
0

注意如果您使用同步,请不要忘记添加-fobjc-exceptions到您的 GCC 标志:

Objective-C 提供了对线程同步和异常处理的支持,这在本文和“异常处理”中进行了解释。要打开对这些功能的支持,请使用 GNU 编译器集合 (GCC) 3.3 版及更高版本的 -fobjc-exceptions 开关。

http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/ObjectiveC/Articles/ocThreading.html

于 2010-09-22T17:44:49.510 回答
0

使用对象的副本来修改它。由于您正在尝试修改数组(集合)的引用,而其他人也可能会修改它(多次访问),因此创建副本对您有用。创建一个副本,然后枚举该副本。

NSMutableArray *originalArray = @[@"A", @"B", @"C"];
NSMutableArray *arrayToEnumerate = [originalArray copy];

现在修改arrayToEnumerate。由于它没有引用 originalArray,而是 originalArray 的副本,因此不会引起问题。

于 2015-10-19T11:28:01.280 回答
0

如果您不想要 Locking 的开销,还有其他方法,因为它有成本。您可以创建一个队列来序列化访问您的关键部分代码的任务,而不是使用锁来保护共享资源(在您的情况下它可能是字典或数组)。队列不会像锁那样受到相同数量的惩罚,因为它不需要陷入内核来获取互斥锁。简单的说

 dispatch_async(serial_queue, ^{
    <#critical code#>
})

如果您希望当前执行等到任务完成,您可以使用

dispatch_sync(serial_queue Or concurrent, ^{
    <#critical code#>
})

一般来说,如果执行不需要等待,异步是首选的方式。

于 2016-06-09T06:53:55.737 回答