Асинхронные задачи в С++11
Доброго времени суток, хотел бы поделиться с сообществом своей небольшой библиотектой.Я программирую на С/C++, и, к сожалению, в рабочих проектах не могу использовать стандарт C++11. Но вот пришли майские праздники, появилось свободное время и я решил поэкспериментировать и по-изучать этот запретный плод. Самое лучшее для изучения чего либо — это практика. Чтение статей о языке программирования научит максимум лучше читать, поэтому я решил написать маленькую библиотеку для асинхронного выполнения функций.Сразу оговорюсь, что я знаю, что существует std: future, std: async и тп. Мне было интересно реализовать самому нечто подобное и окунуться в мир лямбда-функций, потоков и мьютексов с головой. Праздники — отличное время для велопрогулок.Итак начнем
Я решил что моя библиотека будет функционировать следующим образом.Существует некоторый пул, с фиксированным количеством потоков.В него добавляются задачи, используя синтаксис лямбда функций.Из самой задачи можно извлечь результат ее выполнения, или просто дождаться окончания ее работы.Забегая вперед, выглядит это примерно так:
…
act: control control (N_THREADS);
auto some_task = act: make_task ([](std: vector
template
Итак теперь мы можем хранить в объекте лямбда-функцию с параметрами, теперь надо научиться ее вызывать.Для этого необходимо вызвать opertator () у m_func с параметрами, хранящимися в m_vars.С начала я не знал как это сделать, но усиленное использования гугла и переход по второй ссылке принесло результат:
template
template
template
…
public:
void invoke ()
{
ReturnType r = caller (typename gens(m_vars) …);
}
…
Базовый класс задачи
Теперь реализуем базовый класс задачи::
class abstract_task
{
protected:
mutable std: mutex m_mutex;
mutable std: condition_variable m_cond_var;
mutable bool m_complete;
public:
abstract_task (): m_complete (false) {}
virtual ~abstract_task () {}
virtual void invoke () = 0;
virtual void wait () const
{
std: unique_lock
Производительность Я бы не стал писать эту статью скорее всего, если бы не проведенный мной тест производительности данного решения по сравнению с std: async.Конфигурация: Intel® Core™ i7–2600 CPU @ 3.40GHz$gcc --versiongcc (Debian 4.8.2–21) 4.8.2
Тест заключается в параллельном сложении массивов, а затем асинхронном сложении результатов всех сложений. Результатом операции будет:
res = sum (array)*N_P
Числа указаны в миллисекундах.
Тест 1 Оптимизация выключена, количество элементов в массиве 100000000, количество порождаемых задач 73, Количество потоков в пуле 6Результаты: test_act 16775 OKtest_async 16028 OK
Производительность сравнима.Тест 2 Оптимизация включена, количество элементов в массиве 100000000, количество порождаемых задач 73, Количество потоков в пуле 6Результаты: test_act 1597.6 OKtest_async 2530.5 OK
Моя реализация быстрее в полтора раза.Тест 3 Оптимизация включена, количество элементов в массиве 100000000, количество порождаемых задач 73, Количество потоков в пуле 7Результаты: test_act 1313.1 OKtest_async 2503.7 OK
Тест 4 Оптимизация включена, количество элементов в массиве 100000000, количество порождаемых задач 73, Количество потоков в пуле 8Результаты: test_act 1402 OKtest_async 2492.2 OK
Тест 5 Оптимизация включена, количество элементов в массиве 100000000, количество порождаемых задач 173, Количество потоков в пуле 8Результаты: test_act 4435.7 OKtest_async 5789.4 OK
Выводы и баги Данные результаты скорее всего связаны с тем, что async порождает для каждой задачи свой поток, в моей же реализации количество потоков фиксировано и накладные расходы на их создание отсутствуют.Баг — захват переменных области видимости (через []) в лямбда функции вызывает SIGSEGV. Хотя передача их же через параметры работает прекрасно.Не знаю насколько ли полезна данная статья и сама библиотека, но, по крайней мере, я применил некоторые возможности нового стандарта на своей практике.Исходный код