Обзор основных средств реализации многопоточности, Выбор библиотеки для решения задачи - Повышение производительности работы библиотеки GridMD

В качестве доступного инструментария были рассмотрены две открытые кроссплатформенные библиотеки для разработки C++ приложений WxWidgets и Boost, поскольку выбор инструментальных средств ограничен требованием к решению задачи с учетом минимизации использования сторонних средств. Одной из главных особенностей библиотеки GridMD является обеспечение высокой портируемости использующих ее приложений. Обычно проведение расчета на кластере начинается с копирования исходного текста программы на головную машину кластера с его дальнейшей компиляцией установленным на головной машине компилятором и запуском скомпилированных исполняемых файлов. В описанной ситуации обеспечение портируемости означает что, во-первых, код используемых библиотек должен компилироваться широким классом компиляторов, зачастую устаревших. Во-вторых, компиляция приложения должна быть возможна под широкий класс платформ, которые сторонние библиотеки обязаны поддерживать. В-третьих, должна обеспечиваться быстрая настройка среды компиляции и исполнения расчетного приложения на кластере в виде установки необходимых объектных и заголовочных файлов библиотек на каждую из машин кластера, а значит необходимо использовать минимум сторонних зависимостей необходимых для компиляции и исполнения приложения. Таким образом, выбор инструментальных средств ограничен уже используемыми средствами разработки в GridMD. Несмотря на указанное ограничение, библиотеки Boost и WxWidgets поддерживают широкий функционал для работы с многопоточностью. Ниже будет приведен обзор средств работы с потоками в каждой из библиотек, после чего сделан выбор одной из них в качестве главного средства решения задачи.

WxWidgets. Библиотека wxWidgets является кроссплатформенной библиотекой инструментов для разработки приложений c графическим интерфейсом пользователя на языке C++ с открытым исходным кодом [14]. Библиотека wxWidgets является объектно-ориентированной библиотекой, которая предоставляет набор классов для работы с большим множеством компонентов, составляющих современные приложения, таких как элемент графического интерфейса пользователя, файловыми системами, базами данных, объектами мультимедиа и в частности средствами организации многопоточности. Программист на базе единого API имеет возможность писать и компилировать программы множеством различных компиляторов под множество платформ, таких как Microsoft Windows, Unix-подобные системы, Apple Macintosh и других. Отличительными особенностями является либеральность лицензии wxWidgets license под которой распространятся библиотека, допускающая линковку с несвободными фрагментами кода, что позволяет использовать ее в закрытых коммерческих проектах.

Абстракция потока в wxWidgets представлена в виде класса WxThread. Класс WxThread является абстрактным, поэтому невозможно создать объект этого класса. В wxWidgets принята следующая концепция - для создания объекта потока необходимо унаследовать класс WxThread и переопределить чисто виртуальный метод WxThread::Entry(), наполнив его кодом, который будет исполняться в созданном потоке после его запуска. После создания объекта наследника класса WxThread необходимо последовательно вызвать метод инициализации потока WxThread::Create(), передав при необходимости размер стека, выделяемого потоку, и метод запуска потока WxThread::Run(), который неявно запускает переопределенный в дочернем классе метод WxThread::Entry().

В wxWidgets определено два типа объектов потоков в зависимости от механизма управления системными ресурсами - Detached и Joinable. Detached объекты потоков самостоятельно освобождают все связанные с ними ресурсы после завершения функции WxThread::Entry(). После выхода из функции WxThread::Entry() автоматически вызывается деструктор объекта, в котором связанный с объектом поток будет остановлен и освобождена выделенная под объект память. Detached Объекты потоков должны быть выделены в динамической памяти. Фактически, это означает, что программисту не требуется хранить указатель на Detached объект потока, ведь очисткой ресурсов займется сам объект при завершении. Joinable объекты потоков для освобождения ресурсов требуют явного ожидания завершения функции WxThread::Entry() через вызов функции WxThread::Wait(), которая заблокирует вызывающий ее поток до завершения потока, связанного с объектом, для которого был вызван этот метод. Кардинальное отличие двух представленных типов объектов потоков в том, что методы WxThread для Joinable объектов вызывать безопасно в течение всего времени жизни объекта, в отличие от Detached объектов, чье время жизни не определено, поскольку оно зависит только от продолжительности исполнения функции WxThread::Entry(). Завершение потока Joinable объекта можно ожидать в синхронном режиме путем вызова функции WxThread::Wait(), так же, как и проверять состояние его исполнения с помощью методов WxThread::isAlive(), wxThread::isPaused() и WxThread::isRunning(). Явно завершить выполнение потока Joinable объекта можно с помощью метода WxThread::Kill().

В качестве примитивов синхронизации библиотека содержит реализации базовых примитивов семафор WxSemaphore, мьютекс WxMutex и условная переменная WxConditional описанных в предыдущей главе. Поддерживаются рекурсивные мьютексы, которые не блокируют вызывающий поток при многократном блокировании мьютекса одним и тем же потоком. Такой тип мьютекса нельзя использовать в качестве ассоциативного мьютекса для условной переменной.

Функциональность мониторов в библиотеке реализуется классами WxMutexLocker (Листинг 3). В конструкторе объекта WxMutexLocker блокируется мьютекс, который передается в конструктор в качестве параметра. В деструкторе объекта происходит разблокировка связанного с ним мьютекса. Таким образом, объект класса WxMutexLocker обеспечивает взаимоисключающий доступ к критической секции с момента своего создания до момента уничтожения. Работа класса WxMutexLocker является примером идиомы объектно-ориентированного программирования Получение ресурса есть инициализация (RAII), которая позволяет писать безопасный при исключениях код. Объекты классов, реализующих RAII, должны выделяться в стеке в качестве автоматических переменных, тогда при возникновении исключения произойдет Раскрутка стека, представляющая из себя вызов деструкторов всех автоматических переменных, выделенных в функции, породившей исключение. При явном использовании WxMutex в случае порождения исключения может быть не вызван его метод WxMutex::Unlock(), что приведет к перманентной блокировке критической секции, которую этот мьютекс защищает. Документация WxWidgets рекомендует отдавать предпочтение использованию WxMutexLocker для защиты критических секций.

Boost. Проект Boost выступает как набор кроссплатформенных библиотек, позволяющих программисту решать широкий спектр задач, возникающих в его повседневной деятельности [7]. Boost включает в себя инструменты как общего назначения, такие как умные указатели, структуры данных и графы, алгоритмы, так и абстракции для работы со средствами операционной системы, многопоточностью, геометрическими данными, интернационализацией и прочим. Концептуально Boost является расширением стандартной библиотеки STL языка С++ и некоей экспериментальной площадкой, из которой успешные и проверенные программные решения переходят в стандартную библиотеку по решению комитета по стандартизации C++. В реализации Boost сделан упор на обобщенное программирование, широкое использование шаблонов, эффективность и переносимость. Многие библиотеки распространяются в виде заголовочных файлов шаблонных классов, не требующих предварительной сборки и линковки с приложением пользователя, в отличие от отдельно скомпилированных библиотек объектных файлов. В частности, библиотека заголовочных файлов Boost. Graph используется в GridMD для реализации концепции графа исполнения, и не требует отдельной объектной библиотеки для работы скомпилированного пользователем GridMD приложения, обеспечивая высокую переносимость GridMD приложений, о чем было сказано ранее. Большинство библиотек распространяется под лицензией Boost Software License, разрешающей использование Boost как в открытых, так и в коммерческих проектах с закрытым кодом.

В составе Boost имеется библиотека Boost. Thread для работы с многопоточностью [15]. Класс Boost::thread представляет абстракцию потока. Для запуска потока необходимо создать объект класса Boost::thread, передавая в конструктор в качестве параметра объект Вызываемого типа, у которого определен Operator(), Или указатель на функцию, которая будет выполнена в потоке. После создание объекта поток начинает немедленно выполняться.

В случае выхода объекта потока из области видимости, или явного удаления оператором Delete при выделении объекта в динамической памяти поток продолжает выполняться вплоть до завершения функции, переданной в конструктор. Для синхронного ожидания завершения потока необходимо вызвать функцию Boost::thread::join() Или воспользоваться классом Boost::scoped_thread, объект которого при создании инициализируется объектом потока и при необходимости объектом определенного пользователем Вызываемого типа. При выходе объекта Boost::scoped_thread из области видимости по умолчанию гарантируется вызов Join() для связанного с ним потока или определенного пользователем Operator().

Механизм прерывания потока реализован через вызов функции Boost::thread::interrupt(). Как правило, прерывание потока происходит отложено - при достижении определенных Точек прерывания Поток проверяет, не была ли вызвана функция Interrupt(), и если была, то генерирует исключение Boost::thread_interrupted, Которая может быть обработана в функции, исполняемой потоком.

В месте вызова Boost::this_thread::sleep_for будет проверено существование запроса на прерывание и сгенерировано исключение. В общем случае ловить и обрабатывать исключение не обязательно, тогда будет выполнена стандартная схема работы программы при появлении необработанного пользователем исключения в функции - раскрутка стека и выход из потоковой функции. Такое поведение характерно только в контексте прерванного потока, на остальные потоки влияние необработанного исключения оказано не будет. В качестве точек прерывания в библиотеке выделяется места вызова 16 различных функций, которые более подробно описаны в документации, так же поток может быть прерван, если он находится в состоянии ожидания входа в критическую секцию. Если имеется необходимость защитить область потоковой функции от нежелательных проверок на запрос прерывания, то необходимо создать объект класса Boost::this_thread::disable_interruption, в область видимости которого будет отменена проверка запросов на прерывания.

Boost. Thread Имеет реализации всех традиционных примитивов синхронизации, описанных ранее, и поддерживает идиому RAII в виде реализации класса Boost::lock_guard. Кроме того, в Boost поддерживаются концепции Исключительных И Неисключительных блокировок, в виде классов Boost::unique_lock и Boost::shared_lock соответственно, позволяющих реализовать схему Multiple Reader / single Write.

В приведенном примере потокам, печатающего содержимое массива и суммирующего его элементы, необходим лишь доступ на чтение к массиву. Так как потоки, производящие чтение разделяемого ресурса, не влияют на работу друг друга, возможен неисключительный одновременный доступ к ресурсу. Это реализуется с помощью неисключительной блокировки Boost::shared_lock специального типа мьютекса Boost::shared_lock. Для потока, заполняющего массив данных, необходим исключительный доступ, так как он изменяет разделяемый ресурс. С помощью Boost::unique_lock поток приобретает исключительный доступ, сначала дожидаясь завершения доступа каждого из читающих ресурс потоков и блокируя их до завершения изменения ресурса в случае их повторного обращения к ресурсу.

Выбор библиотеки для решения задачи

Библиотека Boost в целом имеет более широкие возможности по организации многопоточности, чем wxWidgets, поддерживая более сложные примитивы синхронизации и абстракции для работы с потоками. Однако библиотека Boost. Thread является отдельно скомпилированной объектной библиотекой, требующей своего распространения вместе с приложением. Более того, необходимо распространять объектные библиотеки Boost. System, Boost. Chrono и Boost. DateTime вместе с приложением, использующим Boost. Thread. В данный момент GridMD не использует объектные библиотеки Boost, но использует объектный модуль wxBase библиотеки wxWidgets для кроссплатформенной работы с файлами, директориями, строками и прочим, в состав который, в частности, входят и классы для работы с потоками. Для сохранения широкой переносимости и легковесности библиотеки GridMD было решено использовать библиотеку WxWidgets в качестве инструмента для распараллеливания выполнения локальных узлов, поскольку она предоставляет весь базовый функционал работы с потоками, необходимый для решения задачи.

Похожие статьи




Обзор основных средств реализации многопоточности, Выбор библиотеки для решения задачи - Повышение производительности работы библиотеки GridMD

Предыдущая | Следующая