Grand Central Dispatch (Grand Central Dispatch)

Перейти к навигации Перейти к поиску

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

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

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

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

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

Особенности платформы[править | править код]

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

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

Примеры[править | править код]

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

Асинхронный вызов[править | править код]

Изначально, у нас имеется приложение с методом 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...\n" );
  });
});

dispatch_release( exampleQueue );

См. также[править | править код]

Ссылки[править | править код]

  1. Apple Previews Mac OS X Snow Leopard to Developers Архивировано 11 июня 2008 года..
  2. GCD libdispatch w/Blocks support working on FreeBSD. Дата обращения: 31 октября 2009. Архивировано 27 июля 2011 года.
  3. FreeBSD Quarterly Status Report. Дата обращения: 31 октября 2009. Архивировано 14 октября 2009 года.
  4. 1 2 3 Apple Technical Brief on Grand Central Dispatch Архивная копия от 20 сентября 2009 на Wayback Machine Архивировано 20 сентября 2009 года..
  5. Grand Central Dispatch (GCD) Reference. Дата обращения: 31 октября 2009. Архивировано из оригинала 9 апреля 2012 года.
  6. Mac OS X 10.6 Snow Leopard: the Ars Technica review. Дата обращения: 28 сентября 2017. Архивировано 9 мая 2012 года.