воскресенье, 30 августа 2009 г.

[comp.concurrency] Стала доступной информация по Apple-вскому Grand Central Dispatch

Вот: Dispatch Queues (развитие темы).

Если вкратце, то получается, что приложению доступно три типа очередей.

Serial-очереди (они же private dispatch queues) создаются по мере необходимости самим приложением. Serial-очередь выполняет поставленные в нее задачи в том порядке, в котором задачи были помещены в нее. Serial-очередь выполняет только одну задачу за один раз. Поэтому Serial-очереди рекомендуют использовать для организации доступа к одному разделяемому ресурсу.

Concurrent-очереди (они же global dispatch queues) создаются самой ОС для приложения. Они способны выполнять сразу несколько задач из очереди, но на выполнение задачи поступают в том же порядке, в котором ставились в очередь. ОС предоставляет приложению три таких очереди – с малым, нормальным и высоким приоритетом.

Main-dispatch-очередь. Это очередь, которая так же создается самой ОС и связывается с главной нитью приложения.

В очереди ставятся задачи. Каждая задача оформляется в виде блока кода. По сути – лямбда-функции. Для поддержки которых Apple расширила С-шные языки: C, Objective-C, C++ (если не ошибаюсь, раньше этого не было на Mac-ах):

dispatch_queue_t myCustomQueue;
myCustomQueue =
  dispatch_queue_create("com.example.MyCustomQueue", NULL);
 
dispatch_async(myCustomQueue, ^{
    printf("Do some work here.\n");
});

В данном примере блок кода содержится в конструкции ^{…}.

Блоки кода могут ссылаться на переменные, которые находятся вне блока (т.е. лексические замыкания). Те переменные, на которые идут ссылки из блока кода, транслятор оформляет в специальную, размещенную в хипе структуру. Что позволяет обращаться к ним даже после выхода из области видимости, в которой блок был создан. Для примитивных типов, вроде int, это совершенно безопасно. Для сложных типов или динамически-выделяемых ресурсов нужно соблюдать осторожность, чтобы не получить “повисшую” ссылку.

Задачи в очереди можно ставить как в асинхронном режиме (см. dispatch_async выше), так и в синхронном. Постановка задачи в синхронном режиме с помощью dispatch_sync означает, что возврат из dispatch_sync будет выполнен только после завершения обработки задачи. Но здесь важно следить, чтобы dispatch_sync не поставил заявку в ту же очередь, которая обслуживает текущий контекст исполнения – это гарантированно приведет к тупикам.

Еще там есть группы очередей (dispatch groups), которые позволяют объединять ряд очередей в одну группу и дожидаться их общего завершения.

Есть специальные семафоры (dispatch semaphores), которые позволяют ограничивать количество задач, которые могут быть одновременно запущены для обработки какого-то ресурса (пример в документации был не очень выразителен, поэтому я не полностью просек эту фичу).

Есть даже специальная функция для организации параллельной обработки циклов. Т.е. вместо:

for(int i = 0; i < 100; ++i ) {
  // Какая-то независимая обработка...
}

можно использовать dispatch_apply:

dispatch_apply(100, queue, ^(size_t i) {
  // Какая-то независимая обработка...
});

Вот такой вот лисапед с квадратными колесами. Имхо, подобную штуку можно замутить и на C++0x с лямбда-функциями. А так получилась своего рода “игла для разработчика” – написанный для MacOS код уже никуда не получится портировать, т.к. в коде будут использованы нестандартные расширения языка.

Да, чуть не забыл – под iPhone этот GCD пока не работает.

Отправить комментарий