Основы Grand Central Dispatch, часть четвертая: всякая всячина
Автор: Mike Ash; оригинал статьи: Intro to Grand Central Dispatch, Part IV: Odds and Ends
Аннотация
В предыдущих трех статьях я представил вам основные составные части Grand Central Dispatch, увлекательного нового API для параллельной обработки задач и событий в Snow Leopard (а теперь и в iOS 4, прим. пер). Сначала были рассмотрены основы и очереди посылок, потом — как правильно учитывать многоядерность современных процессоров, и, наконец, третья неделя была посвящена системе обработки событий GCD. Пришло время рассмотреть все то, что осталось, до чего не дошли руки ранее: приостановка очередей посылок, их планирование, семафоры и единовременная инициализация.
Как и раньше, я предполагаю, что вы прочитали все предыдущие статьи перед чтением этой и уже знакомы со всеми аспектами GCD, которые обсуждались ранее. Если вы еще этого не сделали, пожалуйста, сейчас самое время.
Приостановка работы очередей посылок
Очереди посылок могут быть приостановлены и запущены снова в любой момент. Для приостановки, можно использовать функцию dispatch_suspend, а для возобновления работы — функцию dispatch_resume. Работает это все примерно как ожидается. Стоит только отметить, что функции будут работать и с источниками посылок.
Замечание. Приостановка действует на уровне блоков. То есть, другими словами, dispatch_suspend не приостановит выполнение текущего блока. Вместо этого текущий блок будет завершен в обычном режиме, но новые блоки выполняться не будут, пока очередь (или источник) посылок не возобновит работу.
И еще одно, последнее, замечание. Непосредственно со странички man. Вы обязаны возобновить очередь перед тем, как ее уничтожить, если вы ее перед этим приостановили.
Планирование очередей посылок
У всех пользовательских очередей посылок есть понятие целевой очереди. На самом деле ни одна пользовательская очередь не выполняет никакой работы, вместо этого, передавая ее в целевую очередь на исполнение. Обычно целевая очередь у пользовательской очереди — это глобальная очередь с приоритетом по-умолчанию.
Целевую очередь для пользовательской очереди можно установить при помощи функции dispatch_set_target_queue. Вы можете передать ей любую очередь посылок, даже другую пользовательскую очередь, но не можете создавать циклические зависимости. Можно использовать эту функцию для установки приоритета пользовательской очереди, направляя ее в глобальную очередь с нужным приоритетом. Направив очередь в глобальную с пониженным приоритетом, все задачи вашей очереди будут выполняться с пониженным приоритетом, и то же самое относится к перенаправлению в очередь с повышенным приоритетом.
Другой возможный вариант использования этого — перенаправление пользовательской очереди в главную очередь. Это приведет к тому, что все блоки, которые направлены в пользовательскую очередь, будут направляться в главный поток. Достоинство этого, в отличие от использования главной очереди напрямую в том, что ваша очередь может быть приостановлена или повторно запущена независимо от главной, и может быть потенциально перенаправлена в другую очередь в произвольный момент после. Правда, с этим нужно быть очень осторожным, так как какие-нибудь блоки могут быть критичны к тому, чтобы запускаться в главном потоке.
Еще вариант — перенаправление пользовательских очередей в другие пользовательские очереди. Это позволит задачам из множества очередей выполняться последовательно друг с другом, и можно даже создавать группы очередей, которые могут быть приостановлены все вместе, методом приостановки очереди, в которую они перенаправлены. Для того, чтобы понять, где это может быть использовано, представим себе приложение, которое сканирует набор каталогов и загружает из них файлы. Чтобы недопустить слишком активного использования жесткого диска, вы хотите сделать так, чтобы только одна задача загрузки с диска выполнялась единовременно. Но с разных дисков можно читать параллельно. Чтобы это реализовать, нужно просто построить иерархию очередей посылок, которая отражает иерархию дисков.
Для начала вам потребуется просканировать систему и выяснить, какие диски присутствуют, создав по пользовательской очереди посылок для каждого. После чего вы просканируете файловые системы и создадите очереди и для них, перенаправляя каждую в очередь, которая соответствует диску, на котором находится файловая система. И, наконец, каждый сканер каталога может пользоваться своей собственной очередью, которая направляется в очередь соответствующей файловой системы. Сканеры получают списки файлов каталогов и создают блоки работ для каждого файла, передавая их в свою очередь. Благодаря такой конфигурации системы, все эти блоки будут обращаться к дискам последовательно, и все это будет выполняться абсолютно прозрачно после настройки системы.
Семафоры
Семафоры посылок работают точно также как и любые другие семафоры, и если вы знакомы с ними в других многопоточных системах, все дальнейшее будет вам очень знакомо.
Семафор — это просто целое число, у которого есть некое начальное значение и которое позволяет проделывать две операции: signal (сигнал) и wait (подождать). Сигнал обозначает, что значение должно быть увеличено на единицу. Когда же поток ждет семафор, он блокируется, если необходимо, до тех пор, пока значение семафора не станет больше нуля, после чего уменьшает значение на единицу.
Семафоры посылок создаются, используя функцию dispatch_semaphore_create, сигнал посылается при помощи dispatch_semaphore_signal, а подождать семафор можно, вызвав функцию dispatch_semaphore_wait. Страницы man по этим функциям дают два отличных примера использования семафоров, один из которых включает в себя синхронизацию завершения работы, а другой — контроль доступа к ограниченному ресурсу. Вместо того, чтобы придумывать свои, неинтересные примеры, я призываю вас просто прочитать эти страницы man, чтобы рассмотреть потенциальные возможности семафоров.
Единовременная инициализация
В GCD также есть поддержка единовременной инициализации (one-time initialization), которая вам знакома по pthreads и делает примерно то же самое, что и pthread_once. Главное достоинство подхода GCD в том, что он использует блоки вместо указателей на функции, обеспечивая более естественный код.
Главным образом использовать эту возможность стоит для ленивой инициализации синглтонов или других данных с доступом из разных потоков «безопасным в смысле многопотокового доступа» способом. Обычно инициализация синглтона выглядит примерно так:
+ (id)sharedWhatever {
static Whatever *whatever = nil;
@synchronized([Whatever class]) {
if(!whatever) {
whatever = [[Whatever alloc] init];
}
}
return whatever;
}
Здесь все правильно, но достаточно затратно. Каждый вызов +sharedWhatever включает в себя получение дорогостоящей блокировки, не смотря на то, что эта блокировка на самом деле нужна лишь единожды. Есть также другие способы «на любителя», которые используют трюки вроде двойной проверки или «атомных» (atomic) операций, но они сложны и в значительной степени подвержены ошибкам.
Используя GCD и dispatch_once тот же код можно переписать следующим образом:
+ (id)sharedWhatever {
static dispatch_once_t pred;
static Whatever *whatever = nil;
dispatch_once(&pred, ^{
whatever = [[Whatever alloc] init];
});
return whatever;
}
Такой вариант несколько проще, чем с @synchronized, и GCD постарается, чтобы все проверки выполнялись быстро. Также, GCD удостоверится, что блок выполнится строго перед тем, как остальные потоки начнут выполнять код за dispatch_once, но, вместе с тем, не заставит код синхронизироваться каждый раз, когда функция вызывается. Если же вы посмотрите на заголовок, где она определена, то обнаружите, что текущая реализация, на самом деле, просто макрос, который выполняет начальную проверку, встраивая ее в код (inline), что обозначает в общем случае полное отсутствие накладных расходов на вызов функций, не говоря уже о синхронизации.
Заключение
На этом мы завершаем серию, посвященную Grand Central Dispatch. В этой статье вы увидели, как приостановить, возобновить работу, перенаправить очередь посылок, вместе с некоторыми примерами использования этих возможностей. Также мы посмотрели на то, как использоваться семафоры посылок и единовременную инициализацию. До этого было рассказано, как работать с объектами посылок, создавать и получать доступ к разным типам очередей посылок и использовать их для разных задач. Научились стратегиям использования выгоды от многоядерных систем и мониторингу событий, используя систему событий GCD. Теперь перед вами полная картина того, как работает GCD и все необходимые знания для того, чтобы писать классный софт!
Ресурсы
Об авторе оригинальной статьи. Его зовут Mike Ash (Майк Эш), он — разработчик под Mac, как он говорит сам на своей домашней страничке, работает на Rogue Amoeba ночью и летает на глайдере днем.
- Оригинал статьи: Intro to Grand Central Dispatch, Part IV: Odds and Ends
спасибо