Используете один общий фоновый поток для обработки данных iOS?

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

Удивительно, однако, что не существует простого способа заставить всю эту работу выполняться в одном совместно используемом потоке, а не создавать новый поток для каждой задачи. Это осложняется большим количеством путей достижения параллелизма, которые, кажется, возникли за эти годы. (Явные NSThreads, NSOperationQueue, НОД и т. д.)

Не переоцениваю ли я накладные расходы, связанные с созданием всех этих потоков? Должен ли я просто не париться и использовать более простые подходы «поток на задачу»? Использовать GCD и предположить, что он умнее меня в отношении (повторного) использования потоков?


person Sixten Otto    schedule 14.11.2011    source источник
comment
Не знаете, почему это не просто? Использование performSelector:onThread:withObject:waitUntilDone: для объекта с глобальным потоком кажется мне довольно простым? Вы ищете более простой метод?   -  person ColdLogic    schedule 14.11.2011
comment
Это просто, только если вы приложили усилия для создания потока и настройки кого-то, чтобы накачать его NSRunloop.   -  person Tommy    schedule 14.11.2011
comment
Точно. Сложность заключается в уходе и подаче нити, а не в том, как отправить ее на работу.   -  person Sixten Otto    schedule 14.11.2011


Ответы (3)


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

[self doCostlyTask];

To:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^()
{
    [self doCostlyTask];

    dispatch_async(dispatch_get_main_queue(), ^()
    {
        // most UIKit tasks are permissible only from the main queue or thread,
        // so if you want to update an UI as a result of the completed action,
        // this is a safe way to proceed
        [self costlyTaskIsFinished];
    });
});

По сути, это говорит ОС «выполнять этот код с низким приоритетом там, где это было бы наиболее эффективно». Различные вещи, которые вы отправляете в любую из глобальных очередей, могут выполняться или не выполняться в одном потоке друг с другом и в потоке, который их отправил, и могут выполняться или не выполняться одновременно. ОС применяет правила, которые считает оптимальными.

Экспозиция:

GCD — это реализация Apple пула потоков, и в то же время они представили замыкания (как «блоки»), чтобы сделать его пригодным для использования. Таким образом, синтаксис ^(C-style args){code} — это блок/замыкание. То есть это код плюс состояние любых переменных (с учетом оговорок), на которые ссылается код. Вы можете хранить и вызывать блоки самостоятельно, не зная и не используя GCD.

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

Так что, насколько развит программист, блоки превращают код во что-то, что вы можете передавать в качестве аргумента. GCD позволяет вам запрашивать выполнение блоков в соответствии с наилучшим расписанием, которым может управлять ОС. Блоки очень легко создавать и копировать — намного легче, чем, например. NSOperationс.

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

person Tommy    schedule 14.11.2011
comment
FWIW, я не совсем смущен тем, как работает GCD или каковы его преимущества. Просто, окажется ли он подходящим инструментом для этой работы. Что больше касается того, перевешивают ли мои дополнительные внешние знания этот конкретный случай универсальный характер НОД. - person Sixten Otto; 14.11.2011
comment
О, хорошо, мы надеемся, что GCD умнее всего, что вы можете построить быстро, и большая часть коммерческого предложения была легковесной; не беспокойтесь об этом, если это поможет. Самым первым тестом, который я провел в версии 10.6, было полнофункциональное матричное умножение — по сути, каждый блок представлял собой крошечный фрагмент кода с четырьмя умножениями, тремя сложениями и сохранением. На моей двухъядерной машине параллельный цикл for с НОД занял почти ровно половину времени традиционного цикла при выполнении тысяч этих вычислений. Так что в моем тесте он оказался действительно необычайно легким. Это помогает? - person Tommy; 14.11.2011
comment
Попытался отредактировать это, чтобы исправить отсутствующее ) после [self costlyTaskIsFinished]; }; , вероятно, должно быть [self costlyTaskIsFinished]; }); но это не позволит мне редактировать, потому что это только одно исправление char. Ну что ж. - person GreenKiwi; 22.05.2012
comment
@GreenKiwi Я думаю, потому что я автор оригинала, это позволило мне добавить недостающую скобку. Хорошее место! - person Tommy; 22.05.2012

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

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

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

Это осложняется большим количеством путей достижения параллелизма, которые, кажется, возникли за эти годы. (Явные NSThreads, NSOperationQueue, GCD и т. д.)

NSOperationQueue использует GCD, поэтому вы можете использовать это, если это упрощает жизнь, чем использование GCD напрямую .

Использовать GCD и предположить, что он умнее меня в отношении (повторного) использования потоков?

Точно.

person Caleb    schedule 14.11.2011

Я бы использовал NSOperationQueue или GCD и профиль. Не могу себе представить, что накладные расходы на поток превзойдут сетевые задержки.

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

person David Dunham    schedule 14.11.2011
comment
Безусловно, с точки зрения настенных часов сетевая задержка будет подавлять время управления потоками. Но с точки зрения работы системы все это ожидание в сети по сути бесплатно, а создание и отключение потоков активно потребляет/конкурирует за ресурсы. - person Sixten Otto; 14.11.2011