Кроме того, интересными дополнениями являются std::async
std::future
: теперь можно оборачивать произвольные нормальные функции в вызовы std::async
, чтобы выполнять их асинхронно в фоновом режиме. Такие обернутые функции возвращают объекты типа std::future
, которые обещают содержать результаты работы функции, и можно сделать что-то еще, прежде чем дождаться их появления. Еще одно значительное улучшение STL — В данной главе мы пройдемся по всем указанным дополнениям, чтобы узнать их самые важные особенности. После этого у нас будет достаточно информации о поддержке параллелизации в STL версии C++17. Мы не станем рассматривать все свойства, только самые важные. Информация, полученная из этой книги, позволит быстро понять остальную часть механизмов распараллеливания, которую можно найти в Интернете в документации к STL версии C++17.
Наконец, в этой главе содержатся два дополнительных примера. В одном из них мы распараллелим отрисовщик множества Мандельброта в ASCII из главы 6, внеся минимальные изменения. В последнем примере реализуем небольшую библиотеку, которая помогает распараллелить выполнение сложных задач неявно и автоматически.
Автоматическое распараллеливание кода, использующего стандартные алгоритмы
В C++17 появилось одно действительно
Для пользователя это значит следующее: если мы уже повсеместно задействуем алгоритмы STL, то можем параллелизовать их работу без особых усилий. Мы легко можем дополнить наши приложения параллелизацией, просто добавив один аргумент, описывающий политику выполнения, в существующие вызовы алгоритмов STL.
В данном разделе мы реализуем простую программу (с не самым серьезным сценарием применения), которая генерирует несколько вызовов алгоритмов STL. При этом увидим, как легко использовать политики выполнения C++17, чтобы запустить их в нескольких потоках. В последних подразделах мы более подробно рассмотрим разные политики выполнения.
Как это делается
В данном примере мы напишем программу, использующую некоторые стандартные алгоритмы. Сама программа является скорее примером того, как могут выглядеть реальные сценарии, а не средством решения настоящей рабочей проблемы. Применяя эти стандартные алгоритмы, мы встраиваем политики выполнения, чтобы ускорить выполнение кода.
1. Сначала включим некоторые заголовочные файлы и объявим об использовании пространства имен std
execution
мы еще не видели, он появился в C++17.#include
#include
#include
#include
#include
using namespace std;
2. В качестве примера создадим функцию-предикат, которая говорит, является ли число четным. Мы воспользуемся ею далее.
static bool odd(int n) { return n%2; }
3. Сначала определим в нашей функции main
int main()
{
vector
4. Чтобы получить большое количество случайных данных для вектора, создадим генератор случайных чисел и распределение и упакуем их в вызываемый объект. Если это кажется странным, пожалуйста, взгляните сначала на примеры, в которых рассматриваются генераторы случайных чисел и распределения в главе 8.
mt19937 gen;
uniform_int_distribution
auto rand_num ([=] () mutable { return dis(gen); });
5. Теперь воспользуемся стандартным алгоритмом std::generate
std::par
, что позволит автоматически распараллелить данный код. Сделав это, позволим нескольким потокам заполнять вектор одновременно; данное действие снижает время выполнения, если компьютер имеет более одного процессора, что обычно верно для современных машин. generate(execution::par