Личный опыт разработки ПО

Сборник рецептов

Записи с меткой ‘Boost’

Многопоточность, баги и списки инициализации

19 комментариев »

Разработка многопоточных программ – задача нетривиальная. Локализовать место ошибки сложно, отладка затруднена, иногда даже возникает желание все свалить на ошибки в сторонних библиотеках и/или компилятор/операционную систему. Так делать не надо. Конечно вероятность такая есть, но она несравненно меньше того, что виновником ошибки являетесь именно вы.

Например приложение работает хорошо, ну скажем в 95 случаях из 100, но в оставшихся пяти оно может зависнуть или вылететь с segmentation fault. А может упасть.

Посмотрим на следующий код:

#include <condition_variable>
#include <iostream>
#include <queue>
#include <mutex>
#include <thread>
 
class Worker
{
public:
    Worker()
        : shutdown_(false)
        , thread_(&Worker::taskProcessor, this)
    {
    }
 
    ~Worker()
    {
        {
            std::lock_guard<decltype(mutex_)> lock(mutex_);
            shutdown_ = true;
        }
 
        hasTask_.notify_one();
 
        thread_.join();
    }
 
    void addTask(int taskId)
    {
        {
            std::lock_guard<decltype(mutex_)> lock(mutex_);
            std::cout << "Adding task " << taskId << std::endl;
            tasks_.push(taskId);
        }
 
        hasTask_.notify_one();
    }
 
private:
    void taskProcessor()
    {
        while (true)
        {
            int task = 0;
 
            {
                std::unique_lock<decltype(mutex_)> lock(mutex_);
 
                if (shutdown_)
                    return;
 
                while (tasks_.empty())
                {
                    hasTask_.wait(lock);
 
                    if (shutdown_)
                        return;
                }
 
                task = tasks_.front();
                tasks_.pop();
            }
 
            // освободили мютекс, чтобы не блокировать другие потоки и делаем работу
            std::cout << "Processing task " << task << std::endl;
        }
    }
 
private:
    bool shutdown_;
 
    std::queue<int> tasks_;
 
    std::thread thread_;
    std::mutex mutex_;
    std::condition_variable hasTask_;
};
 
int main(int argc, char* argv[])
{
    Worker worker;
 
    for (int i = 1; i < 6; ++i)
    {
        worker.addTask(i);
    }
 
    return 0;
}

Здесь приведен простой класс, объект которого обрабатывает некоторые задачи в параллельном потоке. Из основного потока функцией addTask можно добавить задачу в очередь и известить об этом параллельный поток. Поток проснувшись, возьмет из очереди задачу, обработает ее и если задач больше нет – уснет. В деструкторе, прежде чем объект будет уничтожен происходит ожидание обработки всех задач в очереди.

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

Проблема в списке инициализации. Как мы помним, конструирование членов класса происходит в порядке их объявления. В нашем случае сначала будет создан объект типа std::thread и только после него std::mutex. Если звезды сложатся удачно, то запуск параллельного потока займет некоторое время, за которое в основном потоке будет сконструирован мютекс и программа будет работать. Если со звездами не сложиться, то поток начав работу попробует заблокировать мютекс который еще не сконструирован, со всеми вытекающими. Решение простое, но не самое лучшее, так как делает код хрупким – изменить положение членов в списке инициализации:

boost::mutex Mutex_;
std::condition_variable hasTask_;
std::thread thread_;

Лучше будет разделить конструирование и инициализацию:

class Worker
{
public:
	static std::unique_ptr<Worker> create()
	{
		std::unique_ptr<Worker> worker(new Worker());
		worker->init();
		return worker;
	}
 
	...
 
private:
	Worker()
		: shutdown_(false)
	{
	}
 
	void init()
	{
		thread_ = std::thread(&Worker::taskProcessor, this));
	}
 
	...
 
};

15th Ноябрь 2011
21:38

Рубрика: C++

Метки: , ,

Конечные автоматы в C++

11 комментариев »

В статье рассмотрены простые конечные автоматы и их реализация на C++ с помощью switch-конструкций, таблиц времени исполнения и библиотеки Boost Statechart.

Введение

Грубо говоря, конечный автомат (Finite State Machine), глазами пользователя – это черный ящик, в который можно что-то передать и что-то оттуда получить. Это очень удобная абстракция, которая позволяет скрыть сложный алгоритм, кроме того конечные автоматы очень эффективны.

Конечные автоматы изображают в виде диаграмм состоящих из состояний и переходов. Поясню на простом примере:

Как вы наверное догадались – это диаграмма состояний лампочки. Начальное состояние обозначается черным кружком, переходы стрелками, некоторые стрелки подписаны – это события после которых автомат переходит в другое состояние. Итак, сразу из начального состояния, мы попадаем в состояние Light Off – лампа не горит. Если нажать кнопку, то автомат изменит свое состояние и перейдет по стрелке помеченной Push Button, в состояние Light On – лампа горит. Перейти из этого состояния можно опять же по стрелке, после нажатия кнопки, в состояние Light Off.

Также широко используются таблицы переходов:

Текущее состояние Событие Состояние после перехода Действие
Light Off Push Button Light On Загорается лампа
Light On Push Button Light Off Лампа гаснет

Практическое применение автоматов

Конечные автоматы широко используются в программировании. Например очень удобно представить работу устройства в виде автомата. Это сделает код проще и позволит легко с ним экспериментировать и поддерживать.

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

Читать заметку полностью »

13th Февраль 2011
23:05

Рубрика: C++,Алгоритмы

Метки: ,

Архивирование с библиотеками zlib и bzip2/libbzip2 используя Boost Iostreams

Прокомментировать »

Недавно у меня возникла задача добавить сжатие данных в программу. Сразу нашлось две свободных библиотеки – zlib и libbzip2 написанные на C. Изучив вопрос глубже, оказалось что писать удобные обертки над функциями на C не нужно, так как в Boost IOStreams все уже написано.

О том как добавить данный функционал и правильно собрать проект я расскажу в данной заметке. Также будет приведен пример кода для сжатия и распаковки файлов и сравнение zlib (алгоритм DEFLATE, методы gzip и zlib) и libbzip2 (алгоритм bzip2) по скорости работы и уровню сжатия тестовых, бинарных и исполняемых файлов.

Читать заметку полностью »

7th Март 2010
22:59

Рубрика: C++,Разработка

Метки:

Чтение настроек приложения

8 комментариев »

Мне часто приходится писать гибко конфигурируемые программы. Конфигурационные файлы часто получаются довольно сложными, с развитой иерархией. Для хранения настроек я использовал XML файлы, а разбор конфигурации делал вручную.

Недавно (с версии 1.41.0) в Boost появилась библиотека Property Tree, предназначенная для решения данной задачи. Помимо поддержки XML, также поддерживаются форматы INI, JSON и свой формат INFO.

В данной заметке я рассмотрю указанные форматы и приведу код для разбора файла.

Читать заметку полностью »

22nd Февраль 2010
17:23

Boost Optional

Прокомментировать »

В C# при объявлении переменной можно приставить знак вопроса, после чего в дальнейшем проверять была ли переменная инициализирована или нет:

int? a;
...
if (a.HasValue)
{
	...
}

Иногда это бывает очень полезно, например при работе с базами данных.

В C++ к сожалению такого удобства нет, но как известно – программисты C++ отличаются верностью и если язык не предоставляет какую либо возможность, они ее добавляют сами. Например в Boost данный функционал присутствует:

#include <boost/optional.hpp>
...
boost::optional<int> a;
std::cout << (a.is_initialized() ? "Есть значение" : "Нет значения") << std::endl;
boost::optional<int> b(3);
std::cout << (b ? "Есть значение" : "Нет значения") << std::endl;
a = 5;
std::cout << (a ? "Есть значение" : "Нет значения") << std::endl;
int result = a.get() + b.get();
std::cout << result << std::endl;
b.reset();
std::cout << (b ? "Есть значение" : "Нет значения") << std::endl;

Вывод:

Нет значения
Есть значение
Есть значение
8
Нет значения

20th Февраль 2010
22:45

Рубрика: C++

Метки: ,

Boost Test, юнит-тестирование и CMake

10 комментариев »

Boost Test

Написанием модульных тестов можно не только повысить скорость разработки за счет экономии времени на отладке, но и повысить качество. Также написание тестов позволяет критично взглянуть на интерфейсы классов и функций, что выливается в создание простых и логичных интерфейсов. Но разработка с применением тестов может не принести ощутимых плодов из-за сложности написания тестов, что выльется в слабое покрытие кода тестами. Поэтому инструмент для тестирования должен быть максимально простым, написание тестов должно происходить с приложением минимального количества усилий. Я пользовался фреймворком для написания тестов UnitTest++ – это очень хороший и удобный инструмент и если вы не используете Boost, я бы порекомендовал обратить на него пристальное внимание. Но в данной заметке речь пойдет не о нем, а о фреймворке Boost Test.

Читать заметку полностью »

24th Январь 2010
19:09

CMake и Boost

10 комментариев »

В этой заметке я хочу рассмотреть тему сборки проектов использующих библиотеки Boost. Мы рассмотрим проект из исполняемого файла использующего Boost Thread и двух библиотек использующих Boost Unit Test Framework.

Первым делом необходимо установить значения переменных отвечающих за тип линковки библиотек (статическая или динамическая):

set (Boost_USE_STATIC_LIBS ON)

И использование многопоточности библиотеками:

set (Boost_USE_MULTITHREADED ON)

После этого можно выполнить уже знакомую по заметке о CMake и QT команду find_package:

find_package (Boost COMPONENTS список_нужных_модулей REQUIRED)

Дополнительно можно указать необходимую версию Boost:

find_package (Boost 1.35.0 COMPONENTS список_нужных_модулей REQUIRED)

В случае если Boost корректно установлен, результатом работы команды будет создание переменных содержащих директории с заголовочными файлами Boost и пути к необходимым библиотекам:

include_directories(${Boost_INCLUDE_DIRS})
...
target_link_libraries (${PROJECT} ${Boost_LIBRARIES})

Читать заметку полностью »

23rd Январь 2010
18:48

Рубрика: Разработка,Сборка

Метки: ,