Grand Central Dispatch

Материал из Википедии — свободной энциклопедии
Перейти к: навигация, поиск

Grand Central Dispatch (GCD), намёк на название центрального вокзала в Нью-Йорке Grand Central Terminal, технология Apple предназначенная для создания приложений, использующих преимущества многоядерных процессоров и других SMP-систем[1]. Эта технология является реализацией параллелизма задач и основана на шаблоне проектирования «Пул потоков». GCD впервые была представлена в Mac OS X 10.6. Исходные коды библиотеки libdispatch, реализующей сервисы GCD, были выпущены под лицензией Apache 10 сентября 2009 г.[1]. Впоследствии библиотека была портирована[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)analyseDocument:(NSButton *)sender {
    NSDictionary *stats = [myDoc analyse];
    [myModel setDict:stats];
    [myStatsView setNeedsDisplay:YES];
    [stats release];
  }

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

 - (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];
           [stats release];
         });
     });
}

Здесь вызов [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 );

См. также[править | править исходный текст]

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