Проблема с GCD и слишком большим количеством потоков

У меня есть класс загрузчика изображений, который обеспечивает загрузку NSURL и изображение из Интернета и выполняет блок завершения. Код на самом деле довольно простой

- (void)downloadImageWithURL:(NSString *)URLString completion:(BELoadImageCompletionBlock)completion
{
    dispatch_async(_queue, ^{
//    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        UIImage *image = nil;
        NSURL *URL = [NSURL URLWithString:URLString];
        if (URL) {
            image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]];
        }  
        dispatch_async(dispatch_get_main_queue(), ^{
            completion(image, URLString);
        });
    });

}

Когда я заменю

dispatch_async(_queue, ^{

с комментариями

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{

Изображения загружаются намного быстрее, что вполне логично (раньше изображения загружались по одному, теперь их загружается сразу несколько). Моя проблема в том, что у меня около 50 изображений, и я вызываю метод downloadImageWithURL:completion: для всех из них, и когда я использую глобальную очередь вместо _queue, мое приложение в конечном итоге падает, и я вижу более 85 потоков. Может ли проблема заключаться в том, что мой вызов dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) 50 раз подряд приводит к тому, что GCD создает слишком много потоков? Я думал, что gcd обрабатывает все шаги и следит за тем, чтобы количество потоков не было огромным, но если это не так, могу ли я каким-либо образом повлиять на количество потоков?


person dariaa    schedule 04.02.2013    source источник


Ответы (3)


Хорошо из http://developer.apple.com/library/ios/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html

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

а также

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

Отправляя все ваши блоки в параллельную очередь отправки с высоким приоритетом с помощью

[NSData dataWithContentsOfURL:URL]

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

Вы должны отправить на DISPATCH_QUEUE_PRIORITY_BACKGROUND. Эти задачи никоим образом не являются «Высокоприоритетными». Любая обработка изображений должна выполняться, когда есть свободное время и в основном потоке ничего не происходит.

Если вы хотите больше контролировать, сколько из этих вещей происходит одновременно, я рекомендую вам изучить использование NSOperation. Вы можете взять свои блоки и внедрить их в операцию с помощью NSBlockOperation, а затем отправить эти операции в свою собственную NSOperationQueue. У NSOperationQueue есть - (NSInteger)maxConcurrentOperationCount, и в качестве дополнительного преимущества операции также можно отменить после планирования, если это необходимо.

person jackslash    schedule 04.02.2013
comment
Нет, это не имеет ничего общего с приоритетом очереди. GCD только создает дополнительные потоки, когда блокирует существующие потоки для глобального параллельного блока очереди в ядре на нетривиальное количество времени. - person das; 05.02.2013
comment
@das спасибо, я внес некоторые изменения, но, пожалуйста, не стесняйтесь сделать мой ответ еще лучше :) - person jackslash; 05.02.2013

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

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

Если причиной блокировки рабочих потоков в ядре является ввод-вывод (например, +[NSData dataWithContentsOfURL:] в этом примере), лучшим решением будет заменить эти вызовы API, который будет выполнять этот ввод-вывод асинхронно без блокировки, например. NSURLConnection для сетевого или диспетчерского ввода-вывода для ввода-вывода файловой системы.

В качестве альтернативы вы можете ограничить количество одновременных операций блокировки вручную, например. с помощью семафора подсчета отправки.

Эта тема была подробно рассмотрена на сессии WWDC 2012 GCD.

person das    schedule 05.02.2013

Вы можете использовать NSOperationqueue, который поддерживается NSURLConnection

И у него есть следующий метод экземпляра:

- (void)setMaxConcurrentOperationCount:(NSInteger)count
person Mert    schedule 04.02.2013