Использование мьютексов iPhone с асинхронными запросами URL-адресов
Мой клиент iPhone имеет большое участие в асинхронных запросах, много времени постоянно изменяя статические коллекции словарей или массивов. В результате для меня обычно наблюдаются более крупные структуры данных, которые занимают больше времени, чтобы извлечь их с сервера со следующими ошибками:
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <NSCFArray: 0x3777c0> was mutated while being enumerated.'
Обычно это означает, что два запроса на сервер возвращаются с данными, которые пытаются изменить одну и ту же коллекцию. То, что я ищу, - это учебник/пример/понимание того, как правильно структурировать мой код, чтобы избежать этой вредной ошибки. Я верю, что правильный ответ - это мьютексы, но я никогда их лично не использовал.
Это результат создания асинхронных HTTP-запросов с NSURLConnection, а затем использования NSNotification Center в качестве средства делегирования после завершения запросов. При отмене запросов, которые мутируют одни и те же коллекции, мы получаем эти столкновения.
Ответы
Ответ 1
Если возможно, что любые данные (включая классы) будут доступны из двух потоков одновременно, вы должны предпринять шаги для сохранения этих синхронизаций.
К счастью, 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.
}
Ответ 2
Есть несколько способов сделать это. Простейшим в вашем случае, вероятно, будет использование директивы @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 запрещает мутацию объекта, который перечисляется.
Но я думаю, что еще одна проблема заключается в выборе структур данных в многопоточной среде. Строго ли нужно обращаться к вашим словарям/массивам из нескольких потоков? Или фоновые потоки объединяют полученные данные, а затем передают их основному потоку, который будет единственным потоком, доступным для доступа к данным?
Ответ 3
В ответ на ответ sStaticData и NSLock (комментарии ограничены 600 символами), вам не нужно быть очень осторожным в создании объектов sStaticData и NSLock безопасным потоком (чтобы избежать очень маловероятного сценария несколько блокировок, создаваемых разными потоками)?
Я думаю, что есть два способа обхода:
1) Вы можете указать, что эти объекты создаются в начале дня в одном корневом потоке.
2) Определите статический объект, который автоматически создается в начале дня для использования в качестве блокировки, например. статический NSString может быть создан inline:
static NSString *sMyLock1 = @"Lock1";
Тогда я думаю, что вы можете безопасно использовать
@synchronized(sMyLock1)
{
// Stuff
}
В противном случае, я думаю, вы всегда окажетесь в ситуации "курица и яйцо" с созданием ваших замков поточным безопасным способом?
Конечно, вы вряд ли попадете в любую из этих проблем, поскольку большинство iPhone-приложений работают в одном потоке.
Я еще не знаю о предложении [Group semaphore], которое также может быть решением.
Ответ 4
N.B. Если вы используете синхронизацию, не забудьте добавить -fobjc-exceptions
к своим флагам GCC:
Objective-C обеспечивает поддержку синхронизация потоков и исключение обработки, которые объясняются в этом статьи и "Обработка исключений". к включите поддержку этих функций, используйте переключатель -fobjc-exceptions сборник компиляторов GNU (GCC) версии 3.3 и более поздних версий.
http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/ObjectiveC/Articles/ocThreading.html
Ответ 5
Используйте копию объекта для его изменения. Поскольку вы пытаетесь изменить ссылку массива (коллекции), в то время как кто-то другой может также изменить его (множественный доступ), создание копии будет работать для вас. Создайте копию, а затем перечислите эту копию.
NSMutableArray *originalArray = @[@"A", @"B", @"C"];
NSMutableArray *arrayToEnumerate = [originalArray copy];
Теперь измените arrayToEnumerate. Поскольку он не ссылается на originalArray, но является копией исходного массива, это не вызовет проблемы.
Ответ 6
Существуют и другие способы, если вы не хотите накладных расходов на блокировку, поскольку она имеет свои затраты. Вместо использования блокировки для защиты на общем ресурсе (в вашем случае это может быть словарь или массив), вы можете создать очередь для сериализации задачи, которая обращается к вашему критическому разделу кода.
Очередь не принимает ту же сумму штрафа, что и блокировки, поскольку она не требует захвата в ядро для получения мьютекса.
просто поместите
dispatch_async(serial_queue, ^{
<#critical code#>
})
Если вы хотите, чтобы текущее исполнение дождалось завершения задачи, вы можете использовать
dispatch_sync(serial_queue Or concurrent, ^{
<#critical code#>
})
Как правило, если выполнение выполнения не нужно ждать, предпочтительным будет асинхронный процесс.