2009-02-16 36 views
15

我的iPhone客戶端有很多涉及異步請求,很多時間一致地修改字典或數組的靜態集合。其結果,是很常見的我看到這需要較長時間較大的數據結構從具有以下錯誤的服務器中檢索:iPhone對異步URL請求使用互斥鎖

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

這通常意味着兩個請求到服務器回來與試圖數據修改相同的集合。我正在尋找的是關於如何正確構建我的代碼以避免這種有害錯誤的教程/示例/理解。我相信正確的答案是互斥體,但我從未親自使用它們。

這是使用NSURLConnection創建異步HTTP請求,然後在請求完成後使用NSNotification Center作爲委派手段的結果。當發射改變相同集合集的請求時,我們得到這些衝突。

回答

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. 
} 
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 /設置/無論作爲數據存儲。正如你所見,NSFastEnumeration禁止枚舉對象的變異。

但我認爲這裏的另一個問題是在多線程環境中選擇數據結構。是否嚴格需要從多個線程訪問您的字典/數組?或者後臺線程是否可以合併它們接收到的數據,然後將它傳遞給主線程,這將是唯一允許訪問數據的線程?

+0

問題是,'背景'線程不是由我明確創建的。它們是異步NSURLConnection請求的結果。我無法通過代碼與主線程通話。您的其他建議很有用,但我很感激。 – Coocoo4Cocoa 2009-02-16 19:57:29

+0

我相信NSURLConnection的委託將在啓動加載操作的線程上調用,而不一定是創建該對象的線程。所以你可以合併你的委託方法中的數據。 – sbooth 2009-02-17 05:40:13

0

爲響應sStaticData和NSLock答案(評論僅限於600個字符),並不需要非常小心以線程安全的方式創建sStaticData和NSLock對象(避免非常不可能由不同線程創建多個鎖的情況)?

我認爲有兩種解決方法:

1)您可以強制這些對象獲得在一天中的單根線開始創建。

2)定義一個靜態對象,在一天的開始時自動創建以用作鎖,例如,靜態的NSString可以創建在線:

static NSString *sMyLock1 = @"Lock1"; 

那麼我認爲你可以放心地使用

@synchronized(sMyLock1) 
{ 
    // Stuff 
} 

否則我想你會總是在一個「雞和蛋」的局面結束了創建鎖以線程安全的方式?

當然,由於大多數iPhone應用程序在單個線程中運行,因此您不可能遇到任何這些問題。

我不知道[集體信號量]的建議,這也可能是一個解決方案。

0

使用對象的拷貝進行修改。由於您試圖修改數組(集合)的引用,而其他人也可能會修改它(多個訪問),所以創建副本將適用於您。創建一個副本,然後枚舉該副本。

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

現在修改arrayToEnumerate。由於它沒有被引用到originalArray,而是originalArray的副本,所以它不會引起問題。

0

還有其他的方法,如果你不想鎖定的開銷,因爲它有成本。除了使用鎖來保護共享資源(對於您的情況,它可能是字典或數組),您可以創建一個隊列來序列化正在訪問關鍵代碼的任務。 隊列不會像鎖一樣處理懲罰量,因爲它不需要陷入內核來獲取互斥量。 乾脆把

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

在情況下,如果你想當前執行等到任務完成後,您可以使用

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

一般來說,如果執行行的事不用等待,異步是做的首選方式。