Objective c iPhone在异步URL请求中使用互斥体

Objective c iPhone在异步URL请求中使用互斥体,objective-c,iphone,cocoa,multithreading,mutex,Objective C,Iphone,Cocoa,Multithreading,Mutex,我的iPhone客户端大量参与异步请求,大量时间持续修改字典或数组的静态集合。因此,我经常看到较大的数据结构需要较长时间才能从服务器检索,并出现以下错误: *** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <NSCFArray: 0x3777c0> was mutated while being enumerated.' ***由于未捕获的异常“NS

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

*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <NSCFArray: 0x3777c0> was mutated while being enumerated.'
***由于未捕获的异常“NSGenericeException”而终止应用程序,原因:“***集合在枚举时发生了变异。”
这通常意味着向服务器发出的两个请求返回了试图修改同一集合的数据。我想要的是一个教程/示例/了解如何正确构造代码以避免这种有害错误。我确实相信正确的答案是互斥,但我个人从未使用过它们


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

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

@synchronized(sStaticData) {
  // Do something with sStaticData
}
另一种方法是使用NSLock类。创建您想要使用的锁,然后在获取互斥锁时,您将有更大的灵活性(如果锁不可用,则阻塞,等等)

如果您决定采用这两种方法中的任何一种,则必须获取读写锁,因为您使用的是NSMutableArray/Set/where作为数据存储。正如您所看到的,NSFastEnumeration禁止枚举对象的变异


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

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

幸运的是,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.
}

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

我认为有两种解决办法:

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

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

static NSString *sMyLock1 = @"Lock1";
那么我认为你可以安全地使用

@synchronized(sMyLock1) 
{
  // Stuff
}
否则,我认为以线程安全的方式创建锁总是会导致“鸡和蛋”的情况

当然,你不太可能遇到这些问题,因为大多数iPhone应用程序都在一个线程中运行


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

N.B.如果您使用同步,请不要忘记将
-fobjc异常添加到您的GCC标志中:

Objective-C为 线程同步和异常 处理,本节对此进行了解释 第条和“异常处理”。至 启用对这些功能的支持, 使用的-fobjc异常开关 GNU编译器集合(GCC) 版本3.3及更高版本


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

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

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

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

 dispatch_async(serial_queue, ^{
    <#critical code#>
})
dispatch\u async(串行队列^{
})
如果希望当前执行等待任务完成,可以使用

dispatch_sync(serial_queue Or concurrent, ^{
    <#critical code#>
})
调度同步(串行队列或并发)^{
})

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

问题是,“后台”线程不是我显式创建的。它们是异步NSURLConnection请求的结果。我无法通过代码与主线程对话。不过,您的其他建议很有用,我很感激。我相信NSURLConnection的委托将在启动加载操作的线程上调用,而不一定是创建对象的线程。所以你可以在你的委托方法中合并数据。“前面的答案”?现在还有另外两个答案。我现在更新了它以澄清(FTR,我指的是这一个上面的答案-考虑到我提到的sstaticata和NSLock,没有太多歧义)!
dispatch_sync(serial_queue Or concurrent, ^{
    <#critical code#>
})