Программирование >>  Разработка устойчивых систем 

1 ... 170 171 172 [ 173 ] 174 175 176 ... 196


Исполнители

Специальные объекты библиотеки ZThreads, называемые исполнителями, позволяют упростить многопоточное программирование. Исполнители образуют дополнительный уровень абстракции между клиентом и задачей. При их использовании задачу запускает не сам клиент, а промежуточный объект.

Чтобы продемонстрировать эту возможность, мы воспользуемся классом Executor вместо явного создания объектов Thread в примере MoreBasicThreads.cpp. Объект Liftoff умеет решать конкретную задачу; по аналогии с паттерном Команда он предоставляет в распоряжение пользователя единственную функцию. Объект-исполнитель знает, как создать контекст для выполнения объектов Runnable. В следующем примере объект ThreadedExecutor создает отдельный программный поток для каждой задачи:

: с11:ThreadedExecutor.срр

{L} ZThread

linclude <iostream>

#include zthread/ThreadedExecutor.h

linclude LiftOff.h

using namespace ZThread;

using namespace std;

int mainO { try {

ThreadedExecutor executor: for(int i = 0: i < 5; i++) executor.execute(new Liftoff(10. i)): } catch(Synchronization Exception& e) { cerr e.whatO endl;

} /:-

Иногда для создания и управления всеми программными потоками в системе достаточно одного объекта Executor. Многопоточный код в любом случае должен размещаться в блоке try, потому что при возникновении каких-либо проблем функция executeO объекта-исполнителя может запустить исключение Synchronization Exception. Это от110сится ко всем функциям, изменяющим состояние объектов синхронизации, включая запуск потоков, захват мутексов, ожидание по условию и т. д. (см. далее в этой главе).

При завершении всех задач объектом-исполнителем программа немедленно завершается.

В предыдущем примере объект ThreadedExecutor создает программный поток для каждой запускаемой задачи, однако вы легко можете изменить режим выполнения задач, заменив ThreadedExecutor другим типом исполнителя. Для наших целей класс ThreadedExecutor вполне подходит, но в коммерческом коде его применение может привести к чрезмерным затратам на создание многочисленных программных потоков. В таких случаях лучше использовать класс PoolExecutor с ограниченным пулом программных потоков для параллельного выполнения задач:

: СП: Pool Executor, срр

{L} ZThread

linclude <iostream>

Ii nclude zthread/PoblExecutor.h

linclude LiftOff.h



using namespace ZThread: using namespace std:

int mainO { try {

В аргументе конструктора передается минимальное количество программных потоков: Pool Executor executor(5): forCint i - 0: i < 5: i++) executor.executeС new LiftOff(10. i)): } catch(Synchronization Exception& e) { cerr e.whatO endl:

} III:-

При использовании класса PoolExecutor дорогостоящие операции создания программных потоков выполняются заранее и только один раз, после чего программные потоки по мере возможности задействуются заново. Тем самым экономится время, потому что вам не приходится заново платить за создание потока для каждой отдельной задачи. Кроме того, в системах, управляемых событиями, события, для обработки которых необходимы потоки, создаются простой выборкой из пула. Ограниченное количество объектов Thread предотвращает чрезмерное потребление системных ресурсов. Таким образом, хотя в книге мы будем использовать объекты Threaded Executor, рассмотрите возможность применения класса PoolExecutor в окончательном варианте кода.

Класс ConcurrentExecutor ведет себя, как PoolExecutor с вырожденным пулом из одного программного потока. Он хорощо подходит для рещения любых задач с длительным жизненным циклом, например, задачи прослущивания входящих подключений через сокеты. Кроме того, он удобен для выполнения коротких задач в отдельных потоках, например, мелких задач обновления локального или удаленного журнала, задач диспетчеризации событий.

Если ConcurrentExecutor получает сразу несколько задач, то следующая задача запускается только после заверщения предыдущей, поскольку все эти задачи используют один программный поток. Таким образом, класс ConcurrentExecutor организует последовательное выполнение переданных ему задач:

: СИ:ConcurrentExecutor.срр {L} ZThread linclude <iostream>

linclude zthread/ConcurrentExecutor.h linclude LiftOff.h using namespace ZThread: using namespace std:

int mainC) { try {

ConcurrentExecutor executor: forCint i = 0: i < 5: i++) executor.execute(new LiftOffС10. i)): } catchCSynchronization Exception& e) { cerr e.whatO endl:

} III:-

Класс SynchronousExecutor, как и ConcurrentExecutor, используется в ситуациях, когда из нескольких задач должна выполняться только одна. Но в отличие от



ConcurrentExecutor класс SynchronousExecutor не создает собственных программных потоков и не управляет ими. Он задействует поток, предоставивший задачу, а все его функции сводятся к синхронизации. Если п программных потоков поставляют задачи класса SynchronousExecutor, никакие две из этих задач не будут работать одновременно. Очередная задача отрабатывает до завершения, и только после этого стартует следующая задача в очереди.

Допустим, в нескольких потоках выполняются задачи, использующие файловую систему, но для сохранения переносимости программы вы не хотите блокировать файлы функцией flock() или другими специфическими функциями операционной системы. Одно из возможных решений - запустить эти задачи объектом SynchronousExecutor, чтобы в любой момент времени обрабатывалась только одна из них. В этом случае вам не придется заниматься синхронизацией доступа, а файловая система останется в сохранности. Конечно, правильнее было бы синхронизировать доступ к ресурсу (эта тема рассматривается далее), но объект SynchronousExecutor позволяет избавиться от лишних хлопот в черновом варианте программы.

: СП:SynchronousExecutor.срр

{L} ZThread

#1nclude <iostream>

#include zthread/SynchronousExecutor.h #include LiftOff.h using namespace ZThread: using namespace std:

int mainO { try {

SynchronousExecutor executor: fordnt i =0: i < 5: i++) executor, execute (new LiftOffdO. i)): } catch(Synchronization Exception& e) { cerr e.what О endl:

} /:-

При запуске программы вы увидите, что задачи выполняются в порядке предоставления, причем каждая задача завершается перед запуском следующей задачи. Однако мы не видим, чтобы в программе создавались новые потоки: для всех задач используется программный поток main(), поскольку в этом примере именно этот поток поставляет все задачи. Так как класс SynchronousExecutor в основном предназначен для построения прототипов, не стоит задействовать его в окончательной версии кода.

Передача управления

Если вы считаете, что очередная итерация цикла в функции run() (большинство функций run() содержат продолжительные циклы) выполнила достаточный объем работы, подскажите планировщику потоков, что процессор можно временно уступить другому программному потоку. Эта рекомендация (а это именно рекомендация - нет гарантий, что ваша реализация к ней прислушается) принимает форму функции y1eld().

Следующая измененная версия примера с задачей LiftOff уступает управление после каждой итерации:



1 ... 170 171 172 [ 173 ] 174 175 176 ... 196

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика