iPhone использует мьютексы с асинхронными URL-запросами - PullRequest
15 голосов
/ 16 февраля 2009

Мой iPhone-клиент активно участвует в асинхронных запросах, постоянно изменяя статические коллекции словарей или массивов. В результате я обычно вижу большие структуры данных, которые требуют больше времени для извлечения с сервера со следующими ошибками:

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

Обычно это означает, что два запроса к серверу возвращаются с данными, которые пытаются изменить одну и ту же коллекцию. То, что я ищу, - это учебник / пример / понимание того, как правильно структурировать мой код, чтобы избежать этой вредной ошибки. Я верю, что правильный ответ - мьютексы, но я никогда лично ими не пользовался.

Это результат выполнения асинхронных HTTP-запросов с NSURLConnection, а затем использования NSNotification Center в качестве средства делегирования после завершения запросов. При запуске запросов, которые изменяют одни и те же наборы сбора, мы получаем эти коллизии.

Ответы [ 6 ]

28 голосов
/ 16 февраля 2009

Есть несколько способов сделать это. В вашем случае проще всего было бы использовать директиву @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 / что угодно в качестве хранилища данных. Как вы уже видели, NSFastEnumeration запрещает мутацию перечисляемого объекта.

Но я думаю, что другой проблемой здесь является выбор структур данных в многопоточной среде. Строго ли необходим доступ к вашим словарям / массивам из нескольких потоков? Или могут фоновые потоки объединить полученные данные и затем передать их основному потоку, который будет единственным потоком, которому разрешен доступ к данным?

15 голосов
/ 16 февраля 2009

Если возможно, что любые данные (включая классы) будут доступны из двух потоков одновременно, необходимо принять меры для их синхронизации.

К счастью, 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.
}
0 голосов
/ 09 июня 2016

Существуют и другие способы, если вы не хотите накладных расходов на блокировку, поскольку она имеет свою стоимость. Вместо того чтобы использовать блокировку для защиты общего ресурса (в вашем случае это может быть словарь или массив), вы можете создать очередь для сериализации задачи, которая обращается к вашему коду критического раздела. Очередь не требует столько же штрафа, сколько блокировки, так как она не требует захвата ядра для получения мьютекса. Проще говоря

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

В случае, если вы хотите, чтобы текущее выполнение дождалось завершения задачи, вы можете использовать

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

Как правило, если выполнение не нужно ждать, асинхронный является предпочтительным способом.

0 голосов
/ 19 октября 2015

Используйте копию объекта, чтобы изменить его. Поскольку вы пытаетесь изменить ссылку на массив (коллекцию), в то время как кто-то другой может также изменить его (множественный доступ), создание копии будет работать для вас. Создайте копию и затем перечислите ее.

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

Теперь измените arrayToEnumerate. Поскольку он не ссылается на originalArray, но является копией originalArray, это не вызовет проблемы.

0 голосов
/ 22 сентября 2010

N.B. Если вы используете синхронизацию, не забудьте добавить -fobjc-exceptions к вашим флагам GCC:

Objective-C обеспечивает поддержку синхронизация потоков и исключение обработки, которые объясняются в этом статья и «Обработка исключений». включить поддержку этих функций, используйте ключ -fobjc-exception коллекция компиляторов GNU (GCC) версия 3.3 и выше.

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

0 голосов
/ 25 августа 2009

В ответ на ответы sStaticData и NSLock (комментарии ограничены 600 символами) не нужно быть очень осторожным при создании объектов sStaticData и NSLock потокобезопасным способом (чтобы избежать очень маловероятного сценария несколько блокировок создаются разными потоками)?

Я думаю, что есть два обходных пути:

1) Вы можете поручить создание этих объектов в начале дня в едином корневом потоке.

2) Определите статический объект, который автоматически создается в начале дня для использования в качестве блокировки, например, статическая строка NSString может быть создана inline:

static NSString *sMyLock1 = @"Lock1";

Тогда я думаю, что вы можете безопасно использовать

@synchronized(sMyLock1) 
{
  // Stuff
}

В противном случае, я думаю, вы всегда будете в ситуации «курица и яйцо», когда будете создавать свои замки безопасным способом?

Конечно, вы вряд ли столкнетесь с какой-либо из этих проблем, так как большинство приложений для iPhone работают в одном потоке.

Я не знаю о предложении [Групповой семафор] ранее, это также может быть решением.

...