Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Std::future и std::promise




Многопоточное программирование может быть очень сложным, особенно при попытках хитроумного использования потоков и блокировок. И оно может стать еще сложнее, если вам нужно использовать условные переменные и std::atomics (для lock-free программирования). С++11 предоставляет классы future и promise для получения значения из задачи, запущенной в другом потоке, а также класс packaged_task для запуска этих задач. Важное замечание о классах future и promise заключается в том, что они позволяют передать значение между двумя задачами без явного использования блокировки; «система» эффективным образом реализует эту передачу. Основная мысль очень проста: если задача хочет вернуть значение в породивший ее поток, то она помещает значение в promise. Каким-то образом, значение помещается в объект future, связанный с объектом promise. Вызывающий код (обычно это код, запустивший данную задачу) может затем прочитать это значение. Для дополнительного упрощения, см. async.

Стандарт описывает три типа future: future – для самых простых случаев и shared_future и atomic_future для более сложных случаев. Для начала давайте рассмотрим future, поскольку это самый простой тип и он делает все, что мне нужно. Если у нас есть future<X> с именем f, мы можем получить значение типа X путем вызова метода get():

// ожидаем завершения выполнения, в случае необходимости

X v = f.get();

 

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

В некоторых случаях мы не хотим ожидать получение результата, поэтому можем узнать, готов результат или нет:

// значение для get() готово

if (f.wait_for(0)) {

// выполняем некоторые операции

}

else {

// делаем что-то еще

}

 

Однако, главная задача типа future заключается в простом предоставлении результата методом get().

Основная задача типа promise заключается в предоставлении возможности «поместить» (“put”) значение, которое будет затем получено с помощью метода get() объектатипа future. Имена “future” и “promise” являются историческими, так что не вините меня за них. Эти имена являются богатым источником разных каламбуров[1].

Если у вас есть объект типа promise, и вы хотите передать результаты типа X (обратно) во future, то вы можете сделать это двумя способами: передать значение или передать исключение.

try {

X res;

// вычисляем значение res

p.set_value(res);

}

catch (...) { // Ой: не могу получить res

p.set_exception(std::current_exception());

}

 

Это все хорошо, но как мне получить пару соответствующих друг другу объектов future / promise – один объект в моем потоке, а другой – в каком-то другом? Ну, поскольку объекты future и promise могут перемещаться (но не копироваться), то решить это можно самыми разными способами. Наиболее очевидный подход заключается в следующем: при запуске задачи передать ей объект promise и оставить вызывающему коду соответствующий объект future, куда будет помещен результат. Использование async() является наиболее экстремальным и элегантным способом использования этого подхода.

Тип package_task предоставляет простой способ запуска потока для выполнения задачи. В том числе, он заботится об установке объекта future, связанного с соответствующим объектом promise и предоставляет обертку для помещения результата или исключения из задачи в promise. Например:

double comp(vector<double>& v)

{

// упаковываем задачи:

// (в качестве задачи мы используем стандартный

// метод accumulate() для массива double):

packaged_task<double(double*,double*,double)> pt0{

std::accumulate<double*,double*,double>};

packaged_task<double(double*,double*,double)> pt1{

std::accumulate<double*,double*,double>};

auto f0 = pt0.get_future(); // получаем future

auto f1 = pt1.get_future();

pt0(&v[0],&v[v.size()/2],0); // запускаем потоки

pt1(&v[v.size()/2],&v[size()],0);

return f0.get()+f1.get(); // получаем результаты

}

 

См. также:

  • Standard: 30.6 Futures [futures]
  • Anthony Williams: Moving Futures - Proposed Wording for UK comments 335, 336, 337 and 338. N2888==09-0078.
  • Detlef Vollmann, Howard Hinnant, and Anthony Williams An Asynchronous Future Value (revised) N2627=08-0137.
  • Howard E. Hinnant: Multithreading API for C++0X - A Layered Approach. N2094=06-0164. The original proposal for a complete threading package..

 

Std::async()

Метод async(), предназначен для простого запуска задач, является единственной возможностью, которая не утверждена в черновом варианте стандарта. Я ожидаю, что она будет принята в октябре, после переработки двух немного отличающихся предложений.

Вот пример того, как программист может подняться над всеми этими непонятными потоками и блокировками в многопоточном программировании.

// простой функтор аккумулятора

template<class T, class V> struct Accum {

T* b;

T* e;

V val;

Accum(T* bb, T* ee, const V& v): b{bb}, e{ee}, val{vv} {}

V operator() () { return std::accumulate(b,e,val); }

};

double comp(vector<double>& v)

// запускаемнесколько задач, если v содержит

// довольно много элементов

{

if (v.size()<10000)

return std::accumulate(v.begin(),v.end(),0.0);

auto f0 {async(Accum{&v[0],&v[v.size()/4],0.0})};

auto f1 {async(Accum{&v[v.size()/4],&v[v.size()/2],0.0})};

auto f2 {async(Accum{&v[v.size()/2],&v[v.size()*3/4],0.0})};

auto f3 {async(Accum{&v[v.size()*3/4],&v[v.size()],0.0})};

return f0.get()+f1.get()+f2.get()+f3.get();

}

 

Это очень простой способ использования многопоточного программирования (обратите внимание на использования «магических чисел»), но обратите внимание на отсутствие явного использования потоков, блокировок, буферов и т.п. Тип переменных f xопределяется типом возвращаемого значения функции стандартной библиотеки async(), которая возвращает future. В случае необходимости, вызов метода get() объекта типа future ожидает завершения потока. В данном случае, обязанностью метода async() является порождение потоков, а задачей объектов future является вызов join() для ожидания завершения соответствующих потоков. «Простота» является самым важным аспектом дизайна async() / future; тип future может быть использован вручную при работе с потоками, но даже и не думайте использовать async() для запуска задач, выполняющих ввод/вывод, использующих мьютексы или каким-то другим способом взаимодействующих с другими задачами. Идея создания async() аналогична идее, лежащей в основе range-for оператора: предоставить простой механизм обработки самых простых и довольно распространенных случаев, и оставить базовые механизмы для более сложных случаев.

Вызывающий код может указать, чтобы метод async() запускал новый поток, использовал любой поток, кроме вызывающего или же запускал задачу в новом потоке, только если async() «считает», что это того стоит. Последний вариант является самым простым с точки зрения пользователя и потенциально самым эффективным ((только) для простых задач).

См. также:

  • Standard:???
  • Lawrence Crowl: An Asynchronous Call for C++. N2889 = 09-0079.
  • Herb Sutter: A simple async() N2901 = 09-0091.

 

Завершение процесса

Простите, но у меня не было времени для написания этого раздела. Вернитесь к этому разделу позже.

См. также:

  • Abandoning a process

 

Генерация случайных чисел

Случайные числа полезны во многих сферах, таких как тестирование, игры, симуляция и безопасность. Многообразие приложений отражается в многообразии генераторов случайных чисел, представленных в стандартной библиотеке. Генератор случайных чисел состоит из двух частей: движка (engine), генерирующего последовательность случайных или псевдо-случайных значений и распределения (distribution), которое находит соответствие этих значений математическому распределению в некотором диапазоне. Примерами распределений являются uniform_int_distribution (в котором вероятность генерации целочисленного значения является одинаковой) и normal_distribution (распределение в форме «колокола»); каждое для определенного диапазона. Например:

// распределение, соответствующее распределению целых

// чисел в диапазоне 1..6

uniform_int_distribution<int> one_to_six {1,6};

// движок по умолчанию

default_random_engine re {};

 

Для получения случайного значения нужно вызвать метод распределения с указанием движка:

// x является значением в диапазоне [1:6]

int x = one_to_six(re);

 

Постоянная передача движка может оказаться утомительной, поэтому мы можем «связать» (bind) этот аргумент для получения функтора, вызываемого без аргументов:

// создаем генератор

auto dice {bind(one_to_six,re)};

// бросаем кости: x является значением в диапазоне [1:6]

int x = dice();

 

Благодаря бескомпромиссному вниманию к обобщению и производительности, кое-кто считает компонент генерации случайных чисел стандартной библиотеки «тем, чем хочет стать любая библиотека по работе со случайными числами, когда вырастет». Однако ее сложно назвать «простой для новичков» (novice friendly). Я никогда не видел, чтобы интерфейс генерации случайных чисел был узким местом в плане производительности, но я никогда не обучал новичков (с любым опытом), без того чтобы у меня не возникла необходимость в простом генераторе случайных чисел. Этого было бы достаточно:

// генерируем случайное значение равномерного распределения

// в диапазоне [low:high]

int rand_int(int low, high);

 

Но как нам это сделать? Внутри метода rand_int() нам нужно использовать код, аналогичный в примере с игральными костями:

Поделиться:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...