синхронизация отправки и асинхронная отправка в главной очереди
потерпите, это займет некоторое объяснение. У меня есть функция, которая выглядит, как показано ниже.
контекст: "aProject" - это основной объект данных с именем LPProject с массивом с именем 'memberFiles', который содержит экземпляры другого основного объекта данных с именем LPFile. Каждый LPFile представляет собой файл на диске, и то, что мы хотим сделать, это открыть каждый из этих файлов и проанализировать его текст, ища инструкции @import, которые указывают на другие файлы. Если мы найдем операторы @import, мы хотим найдите файл, на который они указывают, а затем "свяжите" этот файл с этим, добавив отношение к сущности core data, представляющей первый файл. Поскольку все это может занять некоторое время на больших файлах, мы сделаем это с помощью основного потока с помощью GCD.
- (void) establishImportLinksForFilesInProject:(LPProject *)aProject {
dispatch_queue_t taskQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (LPFile *fileToCheck in aProject.memberFiles) {
if (//Some condition is met) {
dispatch_async(taskQ, ^{
// Here, we do the scanning for @import statements.
// When we find a valid one, we put the whole path to the imported file into an array called 'verifiedImports'.
// go back to the main thread and update the model (Core Data is not thread-safe.)
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"Got to main thread.");
for (NSString *import in verifiedImports) {
// Add the relationship to Core Data LPFile entity.
}
});//end block
});//end block
}
}
}
вот где все пошло не так:
этот код работает, но я вижу странную проблему. Если я запускаю его на LPProject, который имеет несколько файлов (около 20), он работает отлично. Однако, если я запускаю его на LPProject, который имеет больше файлов (скажем, 60-70), это не правильно бегать. Мы никогда не вернемся к основной теме,NSLog(@"got to main thread");
не появляется и приложение зависает. Но, (и это то, где вещи становятся действительно странными) --- если я сначала запускаю код на маленьком проекте, а затем запускаю его на большом проекте, все работает отлично. Только когда я запускаю код на большом проекте сначала, что проблема появляется.
и вот Кикер, если я изменю вторую строку отправки на это:
dispatch_async(dispatch_get_main_queue(), ^{
(то есть, использовать async
вместо sync
для отправки блока в основную очередь), все работает все время. Отлично. Независимо от количества файлов в проекте!
Я в недоумении, чтобы объяснить такое поведение. Любая помощь или советы о том, что тестирование будут оценены.
3 ответа:
это распространенная проблема, связанная с дисковым вводом/выводом и GCD. В принципе, GCD, вероятно, порождает один поток для каждого файла, и в определенный момент у вас слишком много потоков для обслуживания системы за разумное время.
каждый раз, когда вы вызываете dispatch_async() и в этом блоке вы пытаетесь выполнить любой ввод-вывод (например, похоже, что Вы читаете некоторые файлы здесь), вполне вероятно, что поток, в котором выполняется этот блок кода, заблокируется (будет приостановлен ОС) во время ожидания данных для чтения из файловой системы. Способ работы GCD таков, что когда он видит, что один из его рабочих потоков заблокирован при вводе-выводе, и вы все еще просите его выполнять больше работы одновременно, он просто создаст новый рабочий поток. Таким образом, если вы попытаетесь открыть 50 файлов в параллельной очереди, вполне вероятно, что вы в конечном итоге заставите GCD порождать ~50 потоков.
это слишком много потоков для системы, чтобы осмысленно обслуживать, и вы в конечном итоге голодаете свой основной поток для ЦП.
способ исправить это-использовать последовательную очередь вместо параллельной очереди для выполнения ваших файловых операций. Это легко сделать. Вы захотите создать последовательную очередь и сохранить ее как ivar в своем объекте, чтобы не создавать несколько последовательных очередей. Поэтому удалите этот вызов:
dispatch_queue_t taskQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
добавить это в методе init:
taskQ = dispatch_queue_create("com.yourcompany.yourMeaningfulLabel", DISPATCH_QUEUE_SERIAL);
добавьте это в свой метод dealloc:
dispatch_release(taskQ);
и добавьте это как ivar в объявление класса:
dispatch_queue_t taskQ;
Я считаю, что Райан находится на правильном пути: просто слишком много потоков порождается, когда проект имеет 1500 файлов (количество, с которым я решил протестировать.)
Итак, я переработал код выше, чтобы работать следующим образом:
- (void) establishImportLinksForFilesInProject:(LPProject *)aProject { dispatch_queue_t taskQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(taskQ, ^{ // Create a new Core Data Context on this thread using the same persistent data store // as the main thread. Pass the objectID of aProject to access the managedObject // for that project on this thread's context: NSManagedObjectID *projectID = [aProject objectID]; for (LPFile *fileToCheck in [backgroundContext objectWithID:projectID] memberFiles]) { if (//Some condition is met) { // Here, we do the scanning for @import statements. // When we find a valid one, we put the whole path to the // imported file into an array called 'verifiedImports'. // Pass this ID to main thread in dispatch call below to access the same // file in the main thread's context NSManagedObjectID *fileID = [fileToCheck objectID]; // go back to the main thread and update the model // (Core Data is not thread-safe.) dispatch_async(dispatch_get_main_queue(), ^{ for (NSString *import in verifiedImports) { LPFile *targetFile = [mainContext objectWithID:fileID]; // Add the relationship to targetFile. } });//end block } } // Easy way to tell when we're done processing all files. // Could add a dispatch_async(main_queue) call here to do something like UI updates, etc });//end block }
Итак, в основном, мы теперь создаем один поток, который читает все файлы вместо одного потока на файл. Кроме того, оказывается, что вызов dispatch_async() на main_queue является правильным подходом: рабочий поток будет отправлять этот блок основной поток и не ждать его возвращения, прежде чем приступить к сканированию следующего файла.
эта реализация по существу устанавливает" последовательную " очередь, как предложил Райан(цикл for является его последовательной частью), но с одним преимуществом: когда цикл for заканчивается, мы заканчиваем обработку всех файлов, и мы можем просто вставить блок dispatch_async (main_queue), чтобы делать все, что мы хотим. Это очень хороший способ сказать, когда задача параллельной обработки завершена, и этого не было в моем старая версия.
недостатком здесь является то, что это немного сложнее работать с основными данными на нескольких потоках. Но этот подход кажется пуленепробиваемым для проектов с 5 000 файлами (что является самым высоким, который я тестировал.)
Я думаю, что это более легко разобраться со схемой:
для ситуации, описанной автором:
|taskQ| ***********начало|
/ dispatch_1 ***********|---------
/ dispatch_2 *************|---------
.
/ dispatch_n ***************************|----------
/ главная очередь (синхронизация) / * * начало отправки в главная|
*************************|--dispatch_1--|--dispatch_2--|--dispatch3--|*****************************|--dispatch_n|,
которые делают основную очередь синхронизации настолько занятой, что, наконец, не справляются с задачей.