Grand Central Dispatch

08.05.2021

Grand Central Dispatch (GCD) - технология Apple, предназначенная для создания приложений, использующих преимущества многоядерных процессоров и других SMP-систем. Эта технология является реализацией параллелизма задач и основана на шаблоне проектирования «Пул потоков». GCD впервые была представлена в Mac OS X 10.6. Исходные коды библиотеки libdispatch, реализующей сервисы GCD, были выпущены под лицензией Apache 10 сентября 2009 г.[1]. Впоследствии библиотека была портирована на другую операционную систему FreeBSD .

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

Задача может быть определена как функция либо как «блок». Блок — это нестандартное расширение синтаксиса языков программирования C/C++/Objective-C, позволяющее инкапсулировать код и данные в один объект, аналог замыкания.

Grand Central Dispatch использует потоки на низком уровне, но скрывает детали реализации от программиста. Задачи GCD легковесны, недороги в создании и переключении; Apple утверждает, что добавление задачи в очередь требует лишь 15 процессорных инструкций, в то время как создание традиционного потока обходится в несколько сотен инструкций.

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

Особенности платформы

Платформа GCD объявляет несколько типов данных и функций для создания и манипулирования ими.

  • Dispatch Queues — это объекты, поддерживающие очереди задач (анонимных блоков, либо функций), и запускающие эти задачи в порядке очереди. Библиотека автоматически создаёт несколько очередей с различными уровнями приоритета и выполняет несколько задач одновременно, автоматически выбирая оптимальное число задач для запуска. Пользователь библиотеки может создать любое число последовательных очередей, которые запускают задачи в порядке их добавления, по одной за раз. Поскольку последовательная очередь может выполнять только одну задачу в каждый момент времени, такие очереди можно использовать для синхронизации доступа к разделяемым ресурсам.
  • Dispatch Sources — это объекты, которые позволяют регистрировать блоки или функции для их асинхронного выполнения при срабатывании определённого события.
  • Dispatch Groups — это объекты, позволяющие объединять задачи в группы для последующего объединения (joining). Задачи могут быть добавлены в очередь как члены группы, и затем объект группы может быть использован для ожидания завершения всех задач группы.
  • Dispatch Semaphores — это объекты, которые позволяют не более, чем определённому числу задач выполняться одновременно. См. семафор.

Примеры

Два примера демонстрирующие простоту использования Grand Central Dispatch могут быть найдены в обзоре Snow Leopard Джона Сиракуза на Ars Technica..

Асинхронный вызов

Изначально, у нас имеется приложение с методом analyzeDocument, осуществляющим подсчёт слов и параграфов в документе. Обычно, процесс подсчёта слов и параграфов достаточно быстр и может быть выполнен в главном потоке, без опасений, что пользователь заметит задержку между нажатием кнопки и получением результата:

- (IBAction)analyzeDocument:(NSButton *)sender { NSDictionary *stats = [myDoc analyse]; [myModel setDict:stats]; [myStatsView setNeedsDisplay:YES]; }

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

- (IBAction)analyzeDocument:(NSButton *)sender { dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSDictionary *stats = [myDoc analyze]; dispatch_async(dispatch_get_main_queue(), ^{ [myModel setDict:stats]; [myStatsView setNeedsDisplay:YES]; }); }); }

Здесь вызов [myDoc analyze] помещён в блок, который затем помещается в одну из глобальных очередей. После того, как [myDoc analyze] завершает работу, новый блок помещается в главную очередь, который обновляет интерфейс пользователя. Проведя эти несложные изменения, программист избежал потенциального «подвисания» приложения при анализе больших документов.

Распараллеливание цикла

Второй пример демонстрирует распараллеливание цикла:

for (i = 0; i < count; i++) { results[i] = do_work(data, i); } total = summarize(results, count);

Здесь вызывается функция do_work count раз, результат её i-го выполнения присваивается i-му элементу массива results, затем результаты суммируются. Нет оснований полагать, что do_works полагается на результаты её предыдущих вызовов, поэтому ничто не мешает делать несколько вызовов do_works параллельно. Следующий листинг демонстрирует реализацию этой идеи с помощью GCD:

dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t i){ results[i] = do_work(data, i); }); total = summarize(results, count);

В этом примере dispatch_apply запускает count раз блок, переданный ему, помещая каждый вызов в глобальную очередь и передавая блокам числа от 0 до count-1. Это позволяет ОС выбрать оптимальное число потоков для наиболее полного использования доступных аппаратных ресурсов. dispatch_apply не возвращает управление, пока все его блоки не завершили работу, это позволяет гарантировать, что перед вызовом summarize вся работа изначального цикла выполнена.

Создание последовательных очередей

Разработчик может создать отдельную последовательную очередь для задач, которые должны выполняться последовательно, но могут работать в отдельном потоке. Новая очередь может быть создана таким образом:

dispatch_queue_t exampleQueue; exampleQueue = dispatch_queue_create( "com.example.unique.identifier", NULL ); // exampleQueue может быть использована здесь. dispatch_release( exampleQueue );

Избегайте помещение такой задачи в последовательную очередь, которая помещает другую задачу в ту же самую очередь. Это гарантированно приведёт к взаимоблокировке (deadlock). В следующем листинге продемонстрирован случай такой взаимоблокировки:

dispatch_queue_t exampleQueue = dispatch_queue_create( "com.example.unique.identifier", NULL ); dispatch_sync( exampleQueue, ^{ dispatch_sync( exampleQueue, ^{ printf( "I am now deadlocked... " ); }); }); dispatch_release( exampleQueue );

Имя:*
E-Mail:
Комментарий: