Наш подход заключается в том, что мы заполним вектор, в котором содержатся отдельные символы, элементами future
w*h
элементов, что для нашего случая означает 100*40
, у нас есть вектор, содержащий 4000 значений типа future
, которые высчитываются асинхронно. Если бы наша система имела 4000 ядер ЦП, то это означало бы запуск 4000 потоков, которые выполнили бы перебор множества Мандельброта действительно конкурентно. В обычной системе, имеющей меньшее количество ядер, ЦП будут обрабатывать один асинхронный элемент за другим.В то время как вызов transform
to_iteration_count
сам по себе future
, он возвращает значение практически мгновенно. Оригинальная версия программы к этому моменту будет заблокирована, поскольку итерации занимают слишком много времени.Распараллеленная версия программы
future
. Чтобы это сделать, она вызывает функцию x.get()
для всех значений. Идея заключается в следующем: пока она ждет вывода первого значения, множество других значений высчитываются одновременно. Поэтому, если метод get()
для первого значения типа future
возвращается, то следующий объект типа future
тоже может быть готов к выводу на экран!Если размер вектора будет гораздо больше, то возникнет некоторая измеряемая задержка при создании и синхронизации всех этих значений. В нашем случае задержка не так велика. На моем ноутбуке с процессором Intel i7, имеющем четыре ядра, которые могут работать в режиме
Небольшая автоматическая библиотека для распараллеливания с использованием std::future
Большую часть сложных задач можно разбить на подзадачи. Из всех подзадач можно построить
"foo bar foo bar this that"
, и можем сделать это только путем создания отдельных слов и их объединения с другими словами или с самими собой. Предположим, что этот механизм предоставляется тремя примитивными функциями create
, concat
и twice
.Принимая это во внимание, можно нарисовать следующий DAG, который визуализирует зависимости между ними, что позволяет получить итоговый результат (рис. 9.4).
При реализации данной задачи в коде понятно, что все можно реализовать последовательно на одном ядре ЦП. Помимо этого, все подзадачи, которые не зависят от других подзадач или зависят от уже завершенных, могут быть выполнены
Может показаться утомительным писать подобный код, даже с помощью std::async
create
, concat
и twice
в функции, работающие асинхронно. С их помощью мы найдем действительно элегантный способ создать граф зависимостей. Во время выполнения программы граф сам себя распараллелит, чтобы максимально быстро получить результат.Как это делается
В этом примере мы реализуем некие функции, которые симулируют сложные для вычисления задачи, зависящие друг от друга, и попробуем максимально их распараллелить.
1. Сначала включим все необходимые заголовочные файлы и объявим пространство имен std
#include
#include
#include
#include
#include
#include
using namespace std;
using namespace chrono_literals;
2. Нужно синхронизировать конкурентный доступ к cout
struct pcout : public stringstream {
static inline mutex cout_mutex;
~pcout() {
lock_guard
cout << rdbuf();
cout.flush();
}
};