Очереди отгрузки
Очереди отгрузки Grand Central Dispatch (GCD) являются мощным инструментом для выполнения задач. Очереди отгрузки позволяют Вам выполнить произвольные блоки кода или асинхронно или синхронно относительно вызывающей стороны. Можно использовать очереди отгрузки для выполнения почти всех задач, которые Вы раньше выполняли на отдельных потоках. Преимущество очередей отгрузки состоит в том, что они более просты использовать и намного более эффективный при выполнении тех задач, чем соответствующий потоковый код.
Эта глава обеспечивает введение для диспетчеризации очередей, вместе с информацией о том, как использовать их для выполнения общих задач в приложении. Если Вы хотите заменить существующий потоковый код очередями отгрузки, можно найти некоторые дополнительные подсказки для того, как сделать это в Миграции Далеко от Потоков.
Об очередях отгрузки
Очереди отгрузки являются простым способом выполнить задачи асинхронно и одновременно в Вашем приложении. Задачей является просто некоторая работа, которую должно выполнить Ваше приложение. Например, Вы могли определить задачу выполнить некоторые вычисления, создать или изменить структуру данных, обработать некоторые данные, считанные из файла или любого числа вещей. Вы определяете задачи путем размещения соответствующего кода или в функции или в блочном объекте и добавления его к очереди отгрузки.
Очередь отгрузки является подобной объекту структурой, управляющей задачами, которые Вы представляете ей. Все очереди отгрузки являются структурами данных метода «первым пришел - первым вышел». Таким образом задачи, которые Вы добавляете к очереди, всегда запускаются в том же порядке, что они были добавлены. GCD предоставляет некоторые очереди отгрузки Вам автоматически, но другие можно создать в определенных целях. Таблица 3-1 перечисляет типы очередей отгрузки, доступных Вашему приложению и как Вы используете их.
Ввести | Описание |
---|---|
Последовательный | Последовательные очереди (также известный как частные очереди отгрузки) выполняют одну задачу за один раз в порядке, в котором они добавляются к очереди. В настоящее время выполняющаяся задача работает на отличном потоке (который может варьироваться от задачи до задачи), которым управляет очередь отгрузки. Последовательные очереди часто используются для синхронизации доступа к определенному ресурсу. Можно создать столько последовательных очередей, сколько Вам нужно, и каждая очередь действует одновременно с уважением ко всем другим очередям. Другими словами, если Вы создаете четыре последовательных очереди, каждая очередь выполняет только одну задачу за один раз, но до четырех задач могли все еще выполниться одновременно, один от каждой очереди. Для получения информации о том, как создать последовательные очереди, посмотрите Создающие Последовательные Очереди Отгрузки. |
Параллельный | Параллельные очереди (также известный как тип глобальной очереди отгрузки) выполняют одну или более задач одновременно, но задачи все еще запускаются в порядке, в котором они были добавлены к очереди. В настоящее время выполняющиеся задачи работают на отличных потоках, которыми управляет очередь отгрузки. Точное число задач, выполняющихся в любой данной точке, является переменным и зависит от системных условий. В iOS 5 и позже, можно создать параллельные очереди отгрузки сами путем указания |
Основная очередь отгрузки | Основная очередь отгрузки является глобально доступной последовательной очередью, выполняющей задачи на основном потоке приложения. Эта очередь работает с циклом выполнения приложения (если Вы присутствуете) чередовать выполнение задач с очередями с выполнением других источников событий, присоединенных к циклу выполнения. Поскольку это работает на основном потоке Вашего приложения, основная очередь часто используется в качестве ключевой точки синхронизации для приложения. Несмотря на то, что Вы не должны создавать основную очередь отгрузки, действительно необходимо удостовериться, что приложение истощает ее соответственно. Для получения дополнительной информации о том, как управляют этой очередью, посмотрите Задачи Выполнения на Основном Потоке. |
Когда дело доходит до добавляющего параллелизма к приложению очереди отгрузки обеспечивают несколько преимуществ перед потоками. Самое прямое преимущество является простотой модели программирования рабочего списка. С потоками необходимо записать код и для работы, которую Вы хотите выполнить и для создания и управления самими потоками. Очереди отгрузки позволяют Вам фокусироваться на работе, которую Вы фактически хотите выполнить, не имея необходимость волноваться о создании потока и управлении. Вместо этого система обрабатывает все создание потока и управление для Вас. Преимущество состоит в том, что система в состоянии управлять потоками намного более эффективно, чем какое-либо отдельное приложение когда-нибудь могло. Система может масштабировать число потоков динамично на основе условий существующей системы и доступных ресурсов. Кроме того, система обычно в состоянии начать выполнять Вашу задачу более быстро, чем Вы могли при создании потока сами.
Несмотря на то, что Вы могли бы думать, переписывая Ваш код для очередей отгрузки, будет трудным, часто проще записать код для очередей отгрузки, чем это должно записать код для потоков. Ключ к записи Вашего кода должен разработать задачи, которые являются автономными и в состоянии работать асинхронно. (Это - фактически истина для обоих потоков и очередей отгрузки.) Однако то, где очереди отгрузки имеют преимущество, находится в предсказуемости. Если у Вас есть две задачи, получающие доступ к тому же совместно используемому ресурсу, но работающие на различных потоках, любой поток мог изменить ресурс сначала, и необходимо будет использовать блокировку, чтобы гарантировать, что обе задачи не изменяли тот ресурс одновременно. С очередями отгрузки Вы могли добавить обе задачи к последовательной очереди отгрузки гарантировать, что только одна задача изменила ресурс в любой момент времени. Этот тип основанной на очереди синхронизации более эффективен, чем блокировки, потому что блокировки всегда требуют дорогого прерывания ядра и в оспариваемых и в неоспоримых случаях, тогда как очередь отгрузки работает прежде всего в пространстве процесса Вашего приложения и только раскритиковывает к ядру при необходимости.
Несмотря на то, что Вы были бы правы указать, что две задачи, работающие в последовательной очереди, не работают одновременно, необходимо помнить, что, если два потока берут блокировку одновременно, любой параллелизм, предлагаемый потоками, потерян или значительно сокращен. Что еще более важно потоковая модель требует создания двух потоков, приводящих в рабочее состояние и ядро и память пространства пользователя. Очереди отгрузки не платят тот же штраф памяти за свои потоки, и потоки, которые они используют, заставлены напряженно трудиться и не блокированы.
Некоторые другие ключевые пункты для запоминания об очередях отгрузки включают следующее:
Очереди отгрузки выполняют свои задачи одновременно с уважением к другим очередям отгрузки. Сериализация задач ограничивается задачами в единственной очереди отгрузки.
Система определяет общее количество задач, выполняющихся в любой момент. Таким образом приложение с 100 задачами в 100 различных очередях может не выполнить все те задачи одновременно (если оно не имеет 100 или больше эффективных ядер).
Система принимает приоритетные уровни очереди во внимание при выборе который новые задачи запуститься. Для получения информации о том, как установить приоритет последовательной очереди, посмотрите Обеспечение Очистить Функции Для Очереди.
Задачи в очереди должны быть готовы выполниться в то время, когда они добавляются к очереди. (При использовании объектов операции Какао прежде заметьте, что это поведение отличается от использования операций модели.)
Частные очереди отгрузки считаются на ссылку объекты. В дополнение к сохранению очереди в Вашем собственном коде, знать, что источники отгрузки могут также быть присоединены к очереди и также постепенно увеличить сохранять количество. Таким образом необходимо удостовериться, что все источники отгрузки отменяются, и все сохраняют вызовы, сбалансированы с надлежащего вызова выпуска. Для получения дополнительной информации о сохранении и выпуске очередей, посмотрите управление памятью для Очередей Отгрузки. Для получения дополнительной информации об источниках отгрузки, займитесь Источниками Отгрузки.
Для получения дополнительной информации об интерфейсах Вы используете, чтобы управлять очередями отгрузки, видеть Ссылку Grand Central Dispatch (GCD).
Queue-Related Technologies
Кроме того, для диспетчеризации очередей Центральная Отгрузка обеспечивает несколько технологий, использующих очереди, чтобы помочь управлять кодом. Таблица 3-2 перечисляет эти технологии и обеспечивает ссылки туда, где можно узнать больше информации о них.
Технология | Описание |
---|---|
Группы отгрузки | Группа отгрузки является способом контролировать ряд блочных объектов для завершения. (Можно контролировать блоки синхронно или асинхронно в зависимости от потребностей.) Группы обеспечивают полезный механизм синхронизации для кода, зависящего от завершения других задач. Для получения дополнительной информации об использовании групп посмотрите Ожидание на Группах Задач С очередями. |
Семафоры отгрузки | Семафор отгрузки подобен традиционному семафору, но обычно более эффективен. Семафоры отгрузки раскритиковывают к ядру только, когда вызывающий поток должен быть блокирован, потому что семафор недоступен. Если семафор доступен, никакой вызов ядра не сделан. Для примера того, как использовать семафоры отгрузки, посмотрите Используя Семафоры Отгрузки для Регулирования Использования Конечных Ресурсов. |
Источники отгрузки | Источник отгрузки генерирует уведомления в ответ на определенные типы системных событий. Можно использовать источники отгрузки для слежения за развитием событий, таких как уведомления процесса, сигналы и события дескриптора среди других. Когда событие имеет место, источник отгрузки представляет Ваш код задачи асинхронно указанной очереди отгрузки для обработки. Для получения дополнительной информации о создании и использовании источников отгрузки, посмотрите Источники Отгрузки. |
Реализация задач Используя блоки
Блочные объекты являются функцией языка на базе С, которую можно использовать в C, Objective C и коде C++. Блоки упрощают определять автономное устройство работы. Несмотря на то, что они могли бы казаться сродни указателям функции, блок фактически представлен базовой структурой данных, напоминающей объект и создающейся и управляющейся для Вас компилятором. Пакеты компилятора код Вы обеспечиваете (вместе с любыми связанными данными), и инкапсулирует его в форме, которая может жить в «куче» и быть роздана Ваше приложение.
Одно из главных преимуществ блоков является их возможностью использовать переменные извне их собственного лексического контекста. При определении блока в функции или методе, блочные действия как традиционный блок кода был бы до некоторой степени. Например, блок может считать значения переменных, определенных в родительском объеме. Переменные, к которым получает доступ блок, копируются в блочную структуру данных на «куче» так, чтобы блок мог получить доступ к ним позже. Когда блоки добавляются к очереди отгрузки, эти значения нужно обычно оставлять в формате только для чтения. Однако блоки, выполняющиеся синхронно, могут также использовать переменные, имеющие __block
ключевое слово, предварительно ожидаемое для возврата данных назад объему вызова родителя.
Вы объявляете блоки, встроенные с Вашим кодом с помощью синтаксиса, который подобен синтаксису, используемому для указателей функции. Основное различие между блоком и указателем функции - то, что имени блока предшествуют с каре (^
) вместо звездочки (*
). Как указатель функции, можно передать параметры блоку и получить возвращаемое значение от него. Перечисление 3-1 показывает Вам, как объявить и выполнить блоки синхронно в Вашем коде. Переменная aBlock
как объявляют, блок, берущий единственный целочисленный параметр и не возвращающий значения. Фактический блок, соответствующий тот прототип, тогда присваивается aBlock
и объявленный встроенным. Последняя строка сразу выполняет блок, распечатывая указанные целые числа к стандарту.
Перечисление 3-1 простой блочный пример
int x = 123; |
int y = 456; |
// Block declaration and assignment |
void (^aBlock)(int) = ^(int z) { |
printf("%d %d %d\n", x, y, z); |
}; |
// Execute the block |
aBlock(789); // prints: 123 456 789 |
Следующее является сводкой некоторых ключевых инструкций, которые необходимо рассмотреть при разработке блоков:
Для блоков, что Вы планируете выполнить асинхронно использование очереди отгрузки, безопасно получить скалярные переменные от родительской функции или метода и использовать их в блоке. Однако Вы не должны пытаться получить большие структуры или другие базируемые переменные указателя, выделенные и удаленные контекстом вызова. К тому времени, когда Ваш блок выполняется, память, на которую ссылается тот указатель, может закончиться. Конечно, безопасно выделить память (или объект) самостоятельно и явно вручить от владения той памяти к блоку.
Очереди отгрузки копируют блоки, добавляющиеся к ним, и они выпускают блоки, когда они заканчивают выполняться. Другими словами, Вы не должны явно копировать блоки прежде, чем добавить их к очереди.
Несмотря на то, что очереди более эффективны, чем необработанные потоки при выполнении маленьких задач, там является все еще служебным к созданию блоков и выполнению их на очереди. Если блок выполняет слишком мало работы, может быть более дешево выполнить его встроенный, чем отгрузка это очереди. Способ сказать, выполняет ли блок слишком мало работы, состоит в том, чтобы собрать метрики для каждого пути с помощью инструментов производительности и сравнить их.
Не делайте данных кэша относительно базового потока и ожидайте что данные быть доступными от различного блока. Если задачи в той же очереди должны совместно использовать данные, используйте указатель контекста очереди отгрузки, чтобы хранить данные вместо этого. Для получения дополнительной информации о том, как получить доступ к данным контекста очереди отгрузки, посмотрите Хранящую Пользовательскую Контекстную информацию с Очередью.
Если Ваш блок создает больше, чем несколько объектов Objective C, Вы могли бы хотеть включить части кода своего блока в блоке @autorelease для обработки управления памятью для тех объектов. Несмотря на то, что у очередей отгрузки GCD есть свои собственные пулы автовыпуска, они не делают гарантий относительно того, когда истощены те пулы. Если Ваше приложение является ограниченной памятью, создавание Вашего собственного пула автовыпуска позволяет Вам высвобождать память для автовыпущенных объектов в более равных интервалах.
Для получения дополнительной информации о блоках, включая то, как объявить и использовать их, посмотрите, что Блоки Программируют Темы. Для получения информации о том, как Вы добавляете блоки к очереди отгрузки, видите Добавляющие Задачи Очереди.
Создание и управление очередями отгрузки
Прежде чем Вы добавите свои задачи к очереди, необходимо решить, какую очередь для использования и как Вы намереваетесь использовать ее. Очереди отгрузки могут выполнить задачи или последовательно или одновременно. Кроме того, если у Вас есть определенное использование для очереди в памяти, можно сконфигурировать атрибуты очереди соответственно. Следующие разделы показывают Вам, как создать очереди отгрузки и сконфигурировать их для использования.
Получение глобальных параллельных очередей отгрузки
Параллельная очередь отгрузки полезна, когда у Вас есть многократные задачи, которые могут работать параллельно. Параллельная очередь является все еще очередью, в которой она исключает задачи из очереди в порядке метода «первым пришел - первым вышел»; однако, параллельная очередь может исключить дополнительные задачи из очереди, прежде чем закончатся любые предыдущие задачи. Фактическое число задач, выполняемых параллельной очередью в любой данный момент, является переменным и может измениться динамично как условия в Вашем изменении приложений. Много факторов влияют на число задач, выполняемых параллельными очередями, включая число доступных ядер, объем работы, сделанный другими процессами, и числом и приоритетом задач в других последовательных очередях отгрузки.
Система предоставляет каждому приложению четыре параллельных очереди отгрузки. Эти очереди являются глобальной переменной к приложению и дифференцируются только их приоритетным уровнем. Поскольку они - глобальная переменная, Вы не создаете их явно. Вместо этого Вы просите одну из очередей, использующих dispatch_get_global_queue
функция, как показано в следующем примере:
dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); |
В дополнение к получению параллельной очереди по умолчанию можно также получить очереди с высоким - и низкоприоритетные уровни путем передачи в DISPATCH_QUEUE_PRIORITY_HIGH
и DISPATCH_QUEUE_PRIORITY_LOW
константы к функции вместо этого, или получают очередь фоновых задач путем передачи DISPATCH_QUEUE_PRIORITY_BACKGROUND
постоянный. Как Вы могли бы ожидать, задачи в высокоприоритетной параллельной очереди выполняются перед теми в и низкоприоритетных очередях по умолчанию. Точно так же задачи в очереди по умолчанию выполняются перед теми в низкоприоритетной очереди.
Несмотря на то, что очереди отгрузки считаются на ссылку объекты, Вы не должны сохранять и выпускать глобальные параллельные очереди. Поскольку они - глобальная переменная к Вашему приложению, сохраняют и выпускают требования этих очередей, проигнорированы. Поэтому Вы не должны хранить ссылки на эти очереди. Можно просто вызвать dispatch_get_global_queue
функционируйте каждый раз, когда Вам нужна ссылка на одного из них.
Создание последовательных очередей отгрузки
Когда Вы хотите свои задачи выполниться в определенном порядке, последовательные очереди полезны. Последовательная очередь выполняет только одну задачу за один раз и всегда вытягивает задачи от главы очереди. Вы могли бы использовать последовательную очередь вместо блокировки для защиты совместно используемого ресурса или непостоянной структуры данных. В отличие от блокировки, последовательная очередь гарантирует, что задачи выполняются в предсказуемом порядке. И, пока Вы представляете свои задачи последовательной очереди асинхронно, очередь никогда не может заходить в тупик.
В отличие от параллельных очередей, создающихся для Вас, необходимо явно создать и управлять любыми последовательными очередями, которые Вы хотите использовать. Вы можете создать любое число последовательных очередей для Вашего приложения, но должны избежать создавать большие количества последовательных очередей исключительно как средние значения для выполнения стольких задач одновременно, сколько Вы можете. Если Вы хотите выполнить большие количества задач одновременно, представить им одной из глобальных параллельных очередей. При создании последовательных очередей попытайтесь идентифицировать цель для каждой очереди, такой как защита ресурса или синхронизация некоторого ключевого поведения приложения.
Перечисление 3-2 показывает шаги, требуемые создать пользовательскую последовательную очередь. dispatch_queue_create
функция берет два параметра: имя очереди и ряд атрибутов очереди. Отладчик и инструменты производительности выводят на экран имя очереди, чтобы помочь Вам отследить, как выполняются Ваши задачи. Атрибуты очереди резервируются для будущего использования и должны быть NULL
.
Перечисление 3-2 , Создающее новую последовательную очередь
dispatch_queue_t queue; |
queue = dispatch_queue_create("com.example.MyQueue", NULL); |
В дополнение к любым пользовательским очередям Вы создаете, система автоматически создает последовательную очередь и связывает ее с основным потоком Вашего приложения. Для получения дополнительной информации о получении очереди для основного потока, посмотрите Получение Общих Очередей во Время выполнения.
Получение общих очередей во время выполнения
Центральная Отгрузка обеспечивает функции, чтобы позволить Вам получить доступ к нескольким общим очередям отгрузки из своего приложения:
Используйте
dispatch_get_current_queue
функция для отладки целей или протестировать идентификационные данные текущей очереди. Вызывание этой функции из блочного объекта возвращает очередь, которой был представлен блок (и на котором это теперь по-видимому работает). Вызывание этой функции от за пределами блока возвращает параллельную очередь по умолчанию для Вашего приложения.Используйте
dispatch_get_main_queue
функция для получения последовательной очереди отгрузки связалась с основным потоком приложения. Эта очередь создается автоматически для приложений Какао и для приложений что любой вызовdispatch_main
функционируйте или сконфигурируйте цикл выполнения (использующий любогоCFRunLoopRef
введите илиNSRunLoop
объект) на основном потоке.Используйте
dispatch_get_global_queue
функция для получения любой из совместно используемых параллельных очередей. Для получения дополнительной информации посмотрите Получение Глобальных Параллельных Очередей Отгрузки.
Управление памятью для очередей отгрузки
Очереди отгрузки и другие объекты отгрузки являются считаемыми на ссылку типами данных. При создании последовательной очереди отгрузки она имеет начальный подсчет ссылок 1. Можно использовать dispatch_retain
и dispatch_release
функции, чтобы постепенно увеличиться и постепенно уменьшить тот подсчет ссылок по мере необходимости. Когда подсчет ссылок очереди достигает нуля, система асинхронно освобождает очередь.
Важно сохранить и выпустить объекты отгрузки, такие как очереди, гарантировать, чтобы они остались в памяти, в то время как они используются. Как с управляемыми памятью объектами Какао, общее правило состоит в том, что, если Вы планируете использовать очередь, переданную Вашему коду, необходимо сохранить очередь, прежде чем Вы будете использовать его и выпускать его, когда Вам больше не нужен он. Этот основной образец гарантирует, что очередь остается в памяти столько, сколько Вы используете его.
Даже при реализации собравшего «мусор» приложения необходимо все еще сохранить и выпустить очереди отгрузки и другие объекты отгрузки. Центральная Отгрузка не поддерживает модель сборки «мусора» для предъявления претензий в отношении памяти.
Хранение пользовательской контекстной информации с очередью
Все объекты отгрузки (включая очереди отгрузки) позволяют Вам связывать пользовательские данные контекста с объектом. Чтобы установить и получить эти данные по данному объекту, Вы используете dispatch_set_context
и dispatch_get_context
функции. Система не использует Ваши пользовательские данные всегда, и Вам решать и выделить и освободить данные в подходящее время.
Для очередей можно использовать данные контекста для хранения указателя на объект Objective C или другую структуру данных, помогающую идентифицировать очередь или ее намеченное использование к коду. Можно использовать функцию финализатора очереди, чтобы освободить (или разъединить) данные контекста с очередью, прежде чем это будет освобождено. Пример того, как записать функцию финализатора, очищающую данные контекста очереди, показан в Перечислении 3-3.
Обеспечение очистить функция для очереди
После создания последовательной очереди отгрузки можно присоединить функцию финализатора для выполнения, любой пользовательский очищает, когда освобождена очередь. Очереди отгрузки являются ссылочными считаемыми объектами, и можно использовать dispatch_set_finalizer_f
функция для указания функции, которая будет выполняться, когда подсчет ссылок очереди достигает нуля. Вы используете эту функцию для очистки данных контекста, связанных с очередью, и функция вызвана, только если указатель контекста не NULL
.
Перечисление 3-3 показывает пользовательскую функцию финализатора и функцию, создающую очередь и устанавливающую тот финализатор. Очередь использует функцию финализатора для выпуска данных, хранивших в указателе контекста очереди. ( myInitializeDataContextFunction
и myCleanUpDataContextFunction
функции, на которые ссылаются от кода, являются пользовательскими функциями, которые Вы обеспечили бы, чтобы инициализировать и очистить содержание самой структуры данных.) Указатель контекста, переданный функции финализатора, содержит объект данных, связанный с очередью.
Перечисление 3-3 , Устанавливающее очередь, очищает функцию
void myFinalizerFunction(void *context) |
{ |
MyDataContext* theData = (MyDataContext*)context; |
// Clean up the contents of the structure |
myCleanUpDataContextFunction(theData); |
// Now release the structure itself. |
free(theData); |
} |
dispatch_queue_t createMyQueue() |
{ |
MyDataContext* data = (MyDataContext*) malloc(sizeof(MyDataContext)); |
myInitializeDataContextFunction(data); |
// Create the queue and set the context data. |
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.CriticalTaskQueue", NULL); |
if (serialQueue) |
{ |
dispatch_set_context(serialQueue, data); |
dispatch_set_finalizer_f(serialQueue, &myFinalizerFunction); |
} |
return serialQueue; |
} |
Добавление задач очереди
Для выполнения задачи необходимо диспетчеризировать его соответствующей очереди отгрузки. Можно диспетчеризировать задачи синхронно или асинхронно, и можно диспетчеризировать их отдельно или в группах. Один раз в очереди, очередь становится ответственной за выполнение Ваших задач как можно скорее учитывая его ограничения и существующие задачи уже в очереди. Этот раздел показывает Вам некоторые методы для диспетчеризации задач очереди и описывает преимущества каждого.
Добавление единственной задачи очереди
Существует два способа добавить задачу к очереди: асинхронно или синхронно. Когда возможно, асинхронное выполнение с помощью dispatch_async
и dispatch_async_f
функции предпочтены по синхронной альтернативе. Когда Вы добавляете блочный объект или функцию очереди, нет никакого способа знать, когда выполнится тот код. В результате добавление блоков или функций асинхронно позволяет Вам запланировать выполнение кода и продолжать выполнять другую работу от вызывающего потока. Это особенно важно при планировании задачи от основного потока приложения — возможно, в ответ на некоторое пользовательское событие.
Несмотря на то, что необходимо добавить задачи асинхронно, когда это возможно, могут все еще быть времена, когда необходимо добавить задачу синхронно для предотвращения условий состязания или других ошибок синхронизации. В этих экземплярах можно использовать dispatch_sync
и dispatch_sync_f
функции, чтобы добавить задачу к очереди. Эти функции блокируют текущий поток выполнения, пока указанная задача не заканчивает выполняться.
Следующий пример показывает, как использовать основанные на блоке варианты для диспетчеризации задач асинхронно и синхронно:
dispatch_queue_t myCustomQueue; |
myCustomQueue = dispatch_queue_create("com.example.MyCustomQueue", NULL); |
dispatch_async(myCustomQueue, ^{ |
printf("Do some work here.\n"); |
}); |
printf("The first block may or may not have run.\n"); |
dispatch_sync(myCustomQueue, ^{ |
printf("Do some more work here.\n"); |
}); |
printf("Both blocks have completed.\n"); |
Выполнение блока завершения, когда сделана задача
По их характеру задачи, диспетчеризированные очереди, работают независимо от кода, создавшего их. Однако, когда задача сделана, Ваше приложение могло бы все еще хотеть быть уведомленным относительно того факта так, чтобы это могло включить результаты. С традиционным асинхронным программированием Вы могли бы сделать это использование механизма обратного вызова, но с очередями отгрузки можно использовать блок завершения.
Блок завершения является просто другой частью кода, который Вы диспетчеризируете очереди в конце Вашей исходной задачи. Код вызова обычно обеспечивает блок завершения в качестве параметра, когда это запускает задачу. Весь код задачи должен сделать, представляют указанный блок или функцию указанной очереди, когда это заканчивает свою работу.
Перечисление 3-4 показывает функцию усреднения, реализованную с помощью блоков. Последние два параметра к функции усреднения позволяют вызывающей стороне указывать очередь и блок для использования при создании отчетов о результатах. После того, как функция усреднения вычисляет свое значение, она передает результаты указанному блоку и диспетчеризирует его очереди. Чтобы препятствовать тому, чтобы очередь была выпущена преждевременно, критически важно сохранить ту очередь первоначально и выпустить его, как только был диспетчеризирован блок завершения.
Перечисление 3-4 , Выполняющее обратный вызов завершения после задачи
void average_async(int *data, size_t len, |
dispatch_queue_t queue, void (^block)(int)) |
{ |
// Retain the queue provided by the user to make |
// sure it does not disappear before the completion |
// block can be called. |
dispatch_retain(queue); |
// Do the work on the default concurrent queue and then |
// call the user-provided block with the results. |
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ |
int avg = average(data, len); |
dispatch_async(queue, ^{ block(avg);}); |
// Release the user-provided queue when done |
dispatch_release(queue); |
}); |
} |
Выполнение итераций цикла одновременно
Одно место, где параллельные очереди отгрузки могли бы улучшить производительность, находится в местах, где у Вас есть цикл, выполняющий постоянное число итераций. Например, предположите, что у Вас есть a for
цикл, выполняющий некоторую работу посредством каждой итерации цикла:
for (i = 0; i < count; i++) { |
printf("%u\n",i); |
} |
Если работа, выполняемая во время каждой итерации, отлична от работы, выполняемой во время всех других итераций, и порядок, в котором заканчивается каждый последовательный цикл, неважен, можно заменить цикл вызовом к dispatch_apply
или dispatch_apply_f
функция. Эти функции представляют указанный блок или функцию очереди один раз для каждой итерации цикла. Когда диспетчеризировано параллельной очереди, поэтому возможно выполнить многократные итерации цикла одновременно.
Можно указать или последовательную очередь или параллельную очередь при вызове dispatch_apply
или dispatch_apply_f
. Передача в параллельной очереди позволяет Вам выполнять многократные итерации цикла одновременно и является наиболее распространенным способом использовать эти функции. Несмотря на то, что использование последовательной очереди допустимо и делает правильную вещь для Вашего кода, использование такой очереди не имеет никаких реальных преимуществ производительности перед отъездом цикла на месте.
Перечисление 3-5 показывает, как заменить предыдущее for
цикл с dispatch_apply
синтаксис. Блок Вы передаете в dispatch_apply
функция должна содержать единственный параметр, идентифицирующий текущую итерацию цикла. Когда блок выполняется, значение этого параметра 0
для первой итерации, 1
для второго, и т.д. Значение параметра для последней итерации count - 1
, где count
общее количество итераций.
Перечисление 3-5 , Выполняющее итерации a for
цикл одновременно
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); |
dispatch_apply(count, queue, ^(size_t i) { |
printf("%u\n",i); |
}); |
Необходимо удостовериться, что код задачи делает разумный объем работы посредством каждой итерации. Как с любым блоком или функцией Вы диспетчеризируете очереди, существуют издержки к планированию того кода для выполнения. Если каждая итерация Вашего цикла выполняет только мелкую сумму работы, издержки планирования кода могут перевесить выигрыши в производительности, которых Вы могли бы достигнуть от диспетчеризации его очереди. Если Вы находите, что это - истина во время Вашего тестирования, можно использовать ходьбу для увеличения объема работы, выполняемого во время каждой итерации цикла. С ходьбой Вы группируетесь многократные итерации своего исходного цикла в единственный блок и сокращаете итеративное количество пропорционально. Например, если Вы выполняете 100 итераций первоначально, но решаете использовать шаг 4, Вы теперь выполняете 4 итерации цикла от каждого блока, и Ваше итеративное количество равняется 25. Для примера того, как реализовать ходьбу, посмотрите Изменение к лучшему Кода Цикла.
Выполнение задач на основном потоке
Центральная Отгрузка предоставляет специальной очереди отгрузки, которую можно использовать для выполнения задач на основном потоке приложения. Этой очереди предоставлено автоматически для всех приложений и истощает автоматически любое приложение, устанавливающее цикл выполнения (управляемый любым a CFRunLoopRef
введите или NSRunLoop
объект) на его основном потоке. Если Вы не создаете приложение Какао и не хотите устанавливать цикл выполнения явно, необходимо вызвать dispatch_main
функционируйте для дренажа основной очереди отгрузки явно. Можно все еще добавить задачи к очереди, но если Вы не вызываете эту функцию, те задачи никогда не выполняются.
Можно получить очередь отгрузки для основного потока приложения путем вызова dispatch_get_main_queue
функция. Задачи, добавленные к этой очереди, выполняются последовательно на самом основном потоке. Поэтому можно использовать эту очередь в качестве точки синхронизации для работы, сделанной в других частях приложения.
Используя объекты Objective C в Ваших задачах
GCD предоставляет встроенную поддержку для методов управления памятью Какао, таким образом, можно свободно использовать объекты Objective C в блоках, которые Вы представляете для диспетчеризации очередей. Каждая очередь отгрузки поддерживает свой собственный пул автовыпуска, чтобы гарантировать, что автовыпущенные объекты выпущены в некоторый момент; очереди не делают гарантии о том, когда они фактически выпускают те объекты.
Если Ваше приложение является ограниченной памятью, и Ваш блок создает больше, чем несколько автовыпущенных объектов, создавая Ваш собственный пул автовыпуска являются единственным способом гарантировать, что Ваши объекты выпущены своевременно. Если Ваш блок создает сотни объектов, Вы могли бы хотеть создать больше чем один пул автовыпуска или истощить Ваш пул равномерно.
Для получения дополнительной информации о пулах автовыпуска и управлении памятью Objective C, см. Усовершенствованное Руководство по программированию управления памятью.
Приостановка и возобновление очередей
Можно препятствовать тому, чтобы очередь выполнила блочные объекты временно путем приостановки его. Вы временно отстраняете очередь отгрузки, использующую dispatch_suspend
функция и резюме это с помощью dispatch_resume
функция. Вызов dispatch_suspend
постепенно увеличивает подсчет ссылок приостановки очереди и вызов dispatch_resume
постепенно уменьшает подсчет ссылок. В то время как подсчет ссылок больше, чем нуль, очередь остается временно отстраненной. Поэтому необходимо балансироваться, все приостанавливают вызовы с соответствующим вызовом резюме, чтобы продолжить обрабатывать блоки.
Используя семафоры отгрузки для регулирования использования конечных ресурсов
Если задачи можно хотеть использовать семафор отгрузки для регулирования числа задач, одновременно получающих доступ к тому ресурсу, которые Вы представляете для диспетчеризации доступа очередей некоторый конечный ресурс. Семафор отгрузки работает как регулярный семафор за одним исключением. Когда ресурсы доступны, требуется меньше времени для получения семафора отгрузки, чем это делает для получения традиционного системного семафора. Это вызвано тем, что Центральная Отгрузка не раскритиковывает в ядро за этот особый случай. Единственное время, которое это раскритиковывает в ядро, - когда ресурс не доступен, и система должна парковать Ваш поток, пока не сообщен семафор.
Семантика для использования семафора отгрузки следующие:
Когда Вы создаете семафор (использование
dispatch_semaphore_create
функция), можно указать положительное целое число, указывающее число доступных ресурсов.В каждой задаче вызвать
dispatch_semaphore_wait
ожидать на семафоре.Когда вызов ожидания возвратится, получите ресурс и выполните свою работу.
Когда Вы сделаны с ресурсом, выпускаете его и сигнализируете семафор путем вызова
dispatch_semaphore_signal
функция.
Для примера того, как эти шаги работают, рассмотрите использование дескрипторов файлов в системе. Каждому приложению дают ограниченное количество дескрипторов файлов для использования. Если у Вас есть задача, обрабатывающая большие количества файлов, Вы не хотите открывать столько файлов когда-то, что Вы исчерпываете дескрипторы файлов. Вместо этого можно использовать семафор для ограничения числа дескрипторов файлов в использовании в любой момент кодом обработки файла. Основные части кода, который Вы включили бы в свои задачи, следующие:
// Create the semaphore, specifying the initial pool size |
dispatch_semaphore_t fd_sema = dispatch_semaphore_create(getdtablesize() / 2); |
// Wait for a free file descriptor |
dispatch_semaphore_wait(fd_sema, DISPATCH_TIME_FOREVER); |
fd = open("/etc/services", O_RDONLY); |
// Release the file descriptor when done |
close(fd); |
dispatch_semaphore_signal(fd_sema); |
При создании семафора Вы указываете число доступных ресурсов. Это значение становится начальной переменной количества для семафора. Каждый раз Вы ожидаете на семафоре, dispatch_semaphore_wait
функционируйте декременты, считающие переменную 1. Если получающееся значение отрицательно, функция говорит ядру блокировать Ваш поток. На другом конце, dispatch_semaphore_signal
функционируйте постепенно увеличивает переменную количества 1, чтобы указать, что был высвобожден ресурс. Если существуют блокированные задачи и ожидающий ресурса, один из них впоследствии разблокируется и позволяется выполнить свою работу.
Ожидание на группах задач с очередями
Группы отгрузки являются способом блокировать поток, пока одна или более задач не заканчивают выполняться. Можно использовать это поведение в местах, где Вы не можете сделать успехи, пока все указанные задачи не завершены. Например, после диспетчеризации нескольких задач вычислить некоторые данные, Вы могли бы использовать группу, чтобы ожидать на тех задачах и затем обработать результаты, когда они сделаны. Другой способ использовать группы отгрузки как альтернатива для поточной обработки соединений. Вместо того, чтобы запустить несколько дочерних потоков и затем присоединиться к каждому из них, Вы могли добавить соответствующие задачи к группе отгрузки и ожидать на всей группе.
Перечисление 3-6 показывает базовый процесс для установки группы, диспетчеризации задач к нему и ожидания на результатах. Вместо того, чтобы диспетчеризировать задачи очереди, использующей dispatch_async
функция, Вы используете dispatch_group_async
функция вместо этого. Эта функция связывает задачу с группой и ставит его в очередь для выполнения. Для ожидания на группе задач закончиться Вы тогда используете dispatch_group_wait
функция, передающая в соответствующей группе.
Перечисление 3-6 , Ожидающее на асинхронных задачах
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); |
dispatch_group_t group = dispatch_group_create(); |
// Add a task to the group |
dispatch_group_async(group, queue, ^{ |
// Some asynchronous work |
}); |
// Do some other work while the tasks execute. |
// When you cannot make any more forward progress, |
// wait on the group to block the current thread. |
dispatch_group_wait(group, DISPATCH_TIME_FOREVER); |
// Release the group when it is no longer needed. |
dispatch_release(group); |
Очереди отгрузки и потокобезопасность
Это могло бы казаться нечетным для разговора о потокобезопасности в контексте очередей отгрузки, но потокобезопасность является все еще соответствующей темой. Любое время Вы реализуете параллелизм в своем приложении, существует несколько вещей, которые необходимо знать:
Сами очереди отгрузки ориентированы на многопотоковое исполнение. Другими словами, можно представить задачи очереди отгрузки от любого потока в системе без первого взятия блокировки или синхронизации доступа к очереди.
Не вызывайте
dispatch_sync
функция от задачи, выполняющейся на той же очереди, которую Вы передаете своему вызову функции. Выполнение так заведет в тупик очередь. Если необходимо диспетчеризировать текущей очереди, сделайте так асинхронно использованиеdispatch_async
функция.Избегите брать блокировки от задач, которые Вы представляете очереди отгрузки. Несмотря на то, что безопасно использовать блокировки от Ваших задач при получении блокировки Вы рискуете блокировать последовательную очередь полностью, если та блокировка недоступна. Точно так же для параллельных очередей, ожидающих на блокировке, мог бы препятствовать другим задачам выполниться вместо этого. Если необходимо синхронизировать части кода, используйте последовательную очередь отгрузки вместо блокировки.
Несмотря на то, что можно получить информацию о базовом потоке, выполняющем задачу, лучше избежать делать так. Для получения дополнительной информации о совместимости очередей отгрузки с потоками, посмотрите Совместимость с Потоками POSIX.
Для дополнительных подсказок относительно того, как изменить Ваш существующий потоковый код для использования очередей отгрузки, посмотрите Миграцию Далеко от Потоков.