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 );
См. также
[править | править код]- Блоки (расширение языка Си)
- OpenMP — открытый стандарт для C, C++, Fortran.
- Intel TBB — C++библиотека с открытыми исходниками от Intel.
- Task Parallel Library — .NET-технология, разработанная Microsoft.
- Java Concurrency — Java-технология (также известная как JSR 166).
Ссылки
[править | править код]- ↑ Apple Previews Mac OS X Snow Leopard to Developers.
- ↑ GCD libdispatch w/Blocks support working on FreeBSD
- ↑ FreeBSD Quarterly Status Report
- ↑ 1 2 3 Apple Technical Brief on Grand Central Dispatch.
- ↑ Grand Central Dispatch (GCD) Reference . Дата обращения: 31 октября 2009. Архивировано 10 апреля 2012 года.
- ↑ Mac OS X 10.6 Snow Leopard: the Ars Technica review