dispatch_sync против dispatch_async в главной очереди - PullRequest
52 голосов
/ 30 июня 2011

Потерпи меня, это займет некоторое объяснение.У меня есть функция, похожая на приведенную ниже.

Контекст: «aProject» - это объект Core Data с именем LPProject с массивом с именем «memberFiles», который содержит экземпляры другого объекта Core Data с именем LPFile.Каждый LPFile представляет файл на диске, и мы хотим открыть каждый из этих файлов и проанализировать его текст, ища операторы @import, которые указывают на ДРУГИЕ файлы.Если мы находим операторы @import, мы хотим найти файл, на который они указывают, и затем «связать» этот файл с этим, добавив связь с основным объектом данных, который представляет первый файл.Поскольку все это может занять некоторое время для больших файлов, мы сделаем это из основного потока, используя 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), он NOT работает правильно.Мы никогда не возвращаемся к основному потоку, NSLog(@"got to main thread"); никогда не появляется и приложение зависает.НО, (и это то, где вещи становятся действительно странными) - если я запускаю код для небольшого проекта ПЕРВЫМ, а затем запускаю его для большого проекта, все работает отлично.Проблема возникает только тогда, когда я сначала запускаю код в большом проекте.

А вот кикер, если я изменю вторую строку отправки на эту:

dispatch_async(dispatch_get_main_queue(), ^{

(Этоиспользуйте async вместо sync для отправки блока в основную очередь), все работает все время.В совершенстве.Независимо от количества файлов в проекте!

Я затрудняюсь объяснить это поведение.Буду признателен за любую помощь или советы о том, что проверить дальше.

Ответы [ 3 ]

53 голосов
/ 01 июля 2011

Это распространенная проблема, связанная с дисковым вводом-выводом и GCD.По сути, GCD, вероятно, порождает один поток для каждого файла, и в определенный момент у вас слишком много потоков для обслуживания системы в течение разумного промежутка времени.

Каждый раз, когда вы вызываете dispatch_async () ив этом блоке вы пытаетесь выполнить какой-либо ввод-вывод (например, похоже, что вы читаете здесь некоторые файлы), вполне вероятно, что поток, в котором выполняется этот блок кода, будет блокироваться (приостанавливаться ОС), покаон ожидает чтения данных из файловой системы.GCD работает так, что когда он видит, что один из его рабочих потоков заблокирован на вводе-выводе, и вы все равно просите его выполнять больше работы одновременно, он просто порождает новый рабочий поток.Таким образом, если вы попытаетесь открыть 50 файлов в параллельной очереди, вполне вероятно, что в конечном итоге GCD будет порождать ~ 50 потоков.

Это слишком много потоков для полноценного обслуживания системы, и вы заканчиваетедо истощения вашего основного потока для ЦП.

Способ исправить это - использовать последовательную очередь вместо параллельной очереди для выполнения ваших файловых операций.Это легко сделать.Вы захотите создать последовательную очередь и сохранить ее в виде ивара в своем объекте, чтобы не создавать несколько последовательных очередей.Поэтому удалите этот вызов:

dispatch_queue_t taskQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

Добавьте это в свой метод инициализации:

taskQ = dispatch_queue_create("com.yourcompany.yourMeaningfulLabel", DISPATCH_QUEUE_SERIAL);

Добавьте это в свой метод dealloc:

dispatch_release(taskQ);

И добавьте это как ivar в вашем объявлении класса:

dispatch_queue_t taskQ;

5 голосов
/ 01 июля 2011

Я считаю, что Райан находится на правильном пути: слишком много потоков порождается, когда в проекте 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), чтобы делать все, что мы хотим. Это очень хороший способ узнать, когда задача параллельной обработки завершена, а ее нет в моей старой версии.

Недостатком здесь является то, что работать с Базовыми данными в нескольких потоках немного сложнее. Но этот подход кажется пуленепробиваемым для проектов с 5000 файлов (это самый высокий показатель, который я тестировал).

0 голосов
/ 12 февраля 2015

Я думаю, что это легче понять с помощью диаграммы:

Для ситуации автор описал:

| taskQ | *********** начать |

| dispatch_1 *********** | ---------

| dispatch_2 ************* | ---------

.

| dispatch_n *************************** | ----------

| главная очередь (синхронизация) | ** начало отправки на главную |

************************* | --dispatch_1-- | --dispatch_2-- | --dispatch3-- | *** ************************** | --dispatch_n |

, что делает основную очередь синхронизации настолько загруженной, что, в конце концов, задача не выполняется.

...