Что такое std:: promise?
Я довольно хорошо знаком с C++11's std::thread
,std::async
и std::future
компоненты (например, см. ответ), которые прямолинейны.
однако я не совсем понимаю, что std::promise
- Это, что он делает и в каких ситуациях его лучше использовать. Сам стандартный документ не содержит большого количества информации, выходящей за рамки его синопсиса класса, и также просто:: thread.
может кто-нибудь, пожалуйста, дать краткий, краткий пример ситуация, когда std::promise
необходимо и где это самое идиоматическое решение?
8 ответов:
по словам [фьючерсов.государство] а
std::future
Это асинхронный объект возврата ("объект, который считывает результаты из общего состояния") иstd::promise
это асинхронный поставщик ("объект, который обеспечивает результат для общего состояния") т. е. обещание-это то, что вы set результат, так что вы можете get это из связанного будущем.асинхронный поставщик-это то, что изначально создает общий доступ государство, к которому относится будущее.
std::promise
является одним из типов асинхронного поставщика,std::packaged_task
это другая, и внутренняя детальstd::async
это другое. Каждый из них может создать общее состояние и даст вамstd::future
который разделяет это состояние и может сделать состояние готовым.
std::async
это утилита более высокого уровня удобства, которая дает вам асинхронный объект результата и внутренне заботится о создании асинхронного поставщика и делает общее состояние готовым, когда задача завершает. Вы можете эмулировать его с помощьюstd::packaged_task
(илиstd::bind
иstd::promise
) иstd::thread
но это безопаснее и проще в использованииstd::async
.
std::promise
- это немного более низкий уровень, потому что когда вы хотите передать асинхронный результат в будущее, но код, который делает результат готовым, не может быть завернут в одну функцию, подходящую для передачи вstd::async
. Например, у вас может быть массив из несколькихpromise
s и связанныеfuture
s и имеют один поток, который делает несколько расчеты и устанавливает результат по каждому обещанию.async
позволит вам вернуть только один результат, чтобы вернуть несколько Вам нужно будет позвонитьasync
несколько раз, что может привести к потере ресурсов.
теперь я понимаю ситуацию немного лучше (в немалой степени из-за ответов здесь!), поэтому я подумал, что добавлю немного своей собственной записи.
в C++11 есть два различных, хотя и связанных понятия: асинхронное вычисление (функция, которая вызывается где-то еще) и параллельное выполнение (a thread, что-то, что работает одновременно). Эти два понятия несколько ортогональны. Асинхронные вычисления-это просто другой вкус вызов функции, в то время как поток является контекстом выполнения. Темы полезны сами по себе, но для целей этого обсуждения я буду рассматривать их как деталь реализации.
существует иерархия абстракции для асинхронных вычислений. Например, предположим, что у нас есть функция, которая принимает некоторые аргументы:
int foo(double, char, bool);
во-первых, у нас есть шаблон
std::future<T>
, что представляет собой будущее значение типаT
. Значение может быть получено с помощью функции-членаget()
, который эффективно синхронизирует программу, ожидая результата. Кроме того, будущее поддерживаетwait_for()
, которое можно использовать для того чтобы зондировать ли или не результат уже доступен. Фьючерсы следует рассматривать как асинхронную замену для обычных типов возврата. Для нашего примера функции, мы ожидаемstd::future<int>
.теперь перейдем к иерархии, от Высшего к низшему уровень:
std::async
: наиболее удобный и прямой способ выполнения асинхронных вычислений-черезasync
шаблон функции, который немедленно возвращает соответствующее будущее:auto fut = std::async(foo, 1.5, 'x', false); // is a std::future<int>
у нас очень мало контроля над подробности. В частности, мы даже не знаем, выполняется ли функция одновременно, последовательно по
get()
, или какой-то другой черной магии. Однако, результат легко получается при необходимости:auto res = fut.get(); // is an int
теперь мы можем рассмотреть, как реализовать что-то вроде
async
, но таким образом, что мы управление. Например, мы можем настаивать на том, чтобы функция выполнялась в отдельном потоке. Мы уже знаем, что мы можем предоставить отдельный поток с помощьюstd::thread
класса.следующий нижний уровень абстракции делает именно это:
std::packaged_task
. Это шаблон, который обертывает функцию и обеспечивает будущее для возвращаемого значения функции, но сам объект является вызываемым, и его вызов осуществляется по усмотрению пользователя. Мы можем настроить его следующим образом:std::packaged_task<int(double, char, bool)> tsk(foo); auto fut = tsk.get_future(); // is a std::future<int>
будущее становится готовым, как только мы вызываем задачу и вызов завершается. Это идеальная работа для отдельного потока. Мы просто должны убедиться, что движение задача в поток:
std::thread thr(std::move(tsk), 1.5, 'x', false);
поток начинает работать немедленно. Мы можем либо
detach
, либоjoin
это в конце области, или когда (например, с помощью Энтони Уильямсаscoped_thread
обертка, которая действительно должна быть в стандартной библиотеке). Подробности использованияstd::thread
не волнуйтесь нас здесь, хотя; просто не забудьте присоединиться или отделитьсяthr
в конце концов. Важно то, что всякий раз, когда вызов функции заканчивается, наш результат готов:auto res = fut.get(); // as before
теперь мы дошли до самого низкого уровня: как бы мы реализовать упакованная задача? Вот где
std::promise
приходит. Обещание-это строительный блок для общения с будущим. Основные шаги таковы:
вызывающий поток дает обещание.
вызывающий поток получает будущее из обещания.
обещание вместе с аргументами функции перемещаются в отдельный поток.
новый поток выполняет функцию и заполняет выполняет обещание.
исходный поток извлекает результат.
в качестве примера, вот наша собственная "упакованная задача":
template <typename> class my_task; template <typename R, typename ...Args> class my_task<R(Args...)> { std::function<R(Args...)> fn; std::promise<R> pr; // the promise of the result public: template <typename ...Ts> explicit my_task(Ts &&... ts) : fn(std::forward<Ts>(ts)...) { } template <typename ...Ts> void operator()(Ts &&... ts) { pr.set_value(fn(std::forward<Ts>(ts)...)); // fulfill the promise } std::future<R> get_future() { return pr.get_future(); } // disable copy, default move };
использование этого шаблона по существу то же самое, что и
std::packaged_task
. Обратите внимание, что перемещение всей задачи включает перемещение обещания. В более специальных ситуациях можно также явно переместить объект promise в Новый Поток и сделать это аргумент функции функции потока, но оболочка задачи, подобная приведенной выше, кажется более гибким и менее навязчивым решением.
делая исключения
обещания тесно связаны с исключениями. Интерфейс обещания сам по себе недостаточно, чтобы передать его состояние полностью, поэтому исключения создаются всякий раз, когда операция над обещанием не имеет смысла. Все исключения имеют тип
std::future_error
, который является производным отstd::logic_error
. Во-первых, описание некоторых ограничений:
построенное по умолчанию обещание неактивно. Неактивные обещания могут умереть без последствий.
обещание становится активным, когда будущее получено через
get_future()
. Правда, только один будущее может быть получен!обещание должно быть выполнено через
set_value()
или есть исключение, установленное черезset_exception()
до окончания срока его службы, если его будущее должно быть уничтожено. Удовлетворенное обещание может умереть без последствий, иget()
становится доступным на будущее. Обещание с исключением вызовет сохраненное исключение при вызовеget()
на будущее. Если обещание умирает без значения или исключения, вызываяget()
на будущее поднимет" нарушенное обещание " исключение.вот небольшая серия тестов, чтобы продемонстрировать эти различные исключительные поведения. Во-первых, жгут проводов:
#include <iostream> #include <future> #include <exception> #include <stdexcept> int test(); int main() { try { return test(); } catch (std::future_error const & e) { std::cout << "Future error: " << e.what() << " / " << e.code() << std::endl; } catch (std::exception const & e) { std::cout << "Standard exception: " << e.what() << std::endl; } catch (...) { std::cout << "Unknown exception." << std::endl; } }
теперь перейдем к тестам.
Случай 1: неактивное обещание
int test() { std::promise<int> pr; return 0; } // fine, no problems
случай 2: активное обещание, неиспользованное
int test() { std::promise<int> pr; auto fut = pr.get_future(); return 0; } // fine, no problems; fut.get() would block indefinitely
Случай 3: Слишком много фьючерсы
int test() { std::promise<int> pr; auto fut1 = pr.get_future(); auto fut2 = pr.get_future(); // Error: "Future already retrieved" return 0; }
случай 4: удовлетворенное обещание
int test() { std::promise<int> pr; auto fut = pr.get_future(); { std::promise<int> pr2(std::move(pr)); pr2.set_value(10); } return fut.get(); } // Fine, returns "10".
случай 5: Слишком много удовлетворению
int test() { std::promise<int> pr; auto fut = pr.get_future(); { std::promise<int> pr2(std::move(pr)); pr2.set_value(10); pr2.set_value(10); // Error: "Promise already satisfied" } return fut.get(); }
такое же исключение выдается, если существует более чем один из или на
set_value
илиset_exception
.Случай 6: Исключение
int test() { std::promise<int> pr; auto fut = pr.get_future(); { std::promise<int> pr2(std::move(pr)); pr2.set_exception(std::make_exception_ptr(std::runtime_error("Booboo"))); } return fut.get(); } // throws the runtime_error exception
случай 7: нарушенное обещание
int test() { std::promise<int> pr; auto fut = pr.get_future(); { std::promise<int> pr2(std::move(pr)); } // Error: "broken promise" return fut.get(); }
Бартош Милевски обеспечивает хорошую рецензию.
C++ разбивает реализацию фьючерсов на множество из маленьких блоков
std:: promise-одна из этих частей.
обещание-это средство передачи возвращаемого значения (или исключение) из потока, выполняющего функцию в поток это наживается на будущем функции.
...
будущее объект синхронизации, построенный вокруг приемный конец канала обещания.
Итак, если вы хотите использовать будущее, вы получаете обещание, которое вы используете для получения результата асинхронной обработки.
пример со страницы:
promise<int> intPromise; future<int> intFuture = intPromise.get_future(); std::thread t(asyncFun, std::move(intPromise)); // do some other stuff int result = intFuture.get(); // may throw MyException
в грубом приближении вы можете рассмотреть
std::promise
как другой конецstd::future
(это ложные, но для иллюстрации вы можете думать, как если бы это было). Потребительский конец канала связи будет использоватьstd::future
чтобы использовать datum из общего состояния, в то время как поток производителя будет использоватьstd::promise
для записи в общее состояние.
std::promise
является каналом или путем для информации, которая будет возвращена из асинхронной функции.std::future
это механизм синхронизации, который заставляет вызывающего абонента ждать, пока возвращаемое значение не будет перенесено вstd::promise
готов (то есть его значение устанавливается внутри функции).
в асинхронной обработке действительно есть 3 основных объекта. В C++11 в настоящее время фокусируется на 2 из них.
основные вещи, которые вам нужно запустить некоторую логику асинхронно:
- The задание (логика упакована как некоторый объект функтора), который будет работать "где-то".
- The фактический узел обработки - поток, процесс и т. д. это запускает такие функторы, когда они предоставляются ему. Посмотрите на Шаблон проектирования "команда" для хорошего представления о том, как это делает базовый пул рабочих потоков.
- The результат обработки: кому-то нужен этот результат, и нужен объект, который получит его для них. По ООП и другим причинам любое ожидание или синхронизация должны выполняться в API этого дескриптора.
C++11 называет вещи, о которых я говорю в (1)
std::promise
, а те в (3)std::future
.std::thread
это единственное, что публично предусмотрено для (2). Это печально, потому что реальные программы должны управлять потоками и ресурсами памяти, и большинство из них хотят, чтобы задачи выполнялись в пулах потоков вместо создания и уничтожения потока для каждой маленькой задачи (что почти всегда вызывает ненужные хиты производительности само по себе и может легко создать голодание ресурсов, что еще хуже).по словам Херба Саттера и других в C++11 brain trust, есть предварительные планы по добавлению
std::executor
это-как и в Java - будет основой для пулов потоков и логически аналогичные настройки для (2). Возможно, мы увидим это в C++2014, но моя ставка больше похожа на C++17 (и Бог поможет нам, если они испортят стандарт для них).
A
std::promise
создается как конечная точка для пары обещание / будущее иstd::future
(создано из std:: promise с помощьюget_future()
метод) - это другая конечная точка. Это простой, один выстрел метод предоставления способа для двух потоков для синхронизации, как один поток предоставляет данные в другой поток через сообщение.вы можете думать об этом, как один поток создает обещание предоставить данные, а другой поток собирает обещание в будущем. Этот механизм может быть использован только однажды.
механизм promise/future-это только одно направление, из потока, который использует
set_value()
метод astd::promise
к потоку, который используетget()
наstd::future
для получения данных. Исключение генерируется, еслиget()
метод будущего вызывается несколько раз.если нить с
std::promise
бset_value()
выполнить свое обещание тогда, когда второй поток вызываетget()
наstd::future
, чтобы собрать обещание, второй поток перейдет в состояние ожидания, пока обещание не будет выполнено первым потоком сstd::promise
при использованииset_value()
метод для отправки данных.в следующем примере кода, простого консольного приложения Visual Studio 2013 для Windows, показано использование нескольких классов/шаблонов параллелизма C++11 и других функций. Он иллюстрирует использование для promise / future, которое хорошо работает, автономные потоки, которые будут выполнять некоторые задачи и останавливаться, и использование, где более синхронное поведение требуется и из-за необходимости нескольких уведомлений пара promise/future не работает.
одна заметка об этом примере-это задержки, добавленные в разных местах. Эти задержки были добавлены только для того, чтобы убедиться, что различные сообщения печатаются на консоль с помощью
std::cout
было бы ясно, и что текст из нескольких потоков не будет смешиваться.первая часть
main()
создает три дополнительных потока и используетstd::promise
иstd::future
отправить данные между потоками. Интересным моментом является то, что основной поток запускает поток T2, который будет ждать данных из основного потока, что-то делать, а затем отправлять данные в третий поток T3, который затем что-то сделает и отправит данные обратно в основной поток.вторая часть
main()
создает два потока и набор очередей, чтобы разрешить несколько сообщений из основного потока в каждый из двух созданных потоков. Мы не можем использоватьstd::promise
иstd::future
для этого, потому что обещание / будущее дуэт один выстрел и не может быть использован повторно.источник для класса
Sync_queue
от Stroustrup в языке программирования C++: 4-е издание.// cpp_threads.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <iostream> #include <thread> // std::thread is defined here #include <future> // std::future and std::promise defined here #include <list> // std::list which we use to build a message queue on. static std::atomic<int> kount(1); // this variable is used to provide an identifier for each thread started. //------------------------------------------------ // create a simple queue to let us send notifications to some of our threads. // a future and promise are one shot type of notifications. // we use Sync_queue<> to have a queue between a producer thread and a consumer thread. // this code taken from chapter 42 section 42.3.4 // The C++ Programming Language, 4th Edition by Bjarne Stroustrup // copyright 2014 by Pearson Education, Inc. template<typename Ttype> class Sync_queue { public: void put(const Ttype &val); void get(Ttype &val); private: std::mutex mtx; // mutex used to synchronize queue access std::condition_variable cond; // used for notifications when things are added to queue std::list <Ttype> q; // list that is used as a message queue }; template<typename Ttype> void Sync_queue<Ttype>::put(const Ttype &val) { std::lock_guard <std::mutex> lck(mtx); q.push_back(val); cond.notify_one(); } template<typename Ttype> void Sync_queue<Ttype>::get(Ttype &val) { std::unique_lock<std::mutex> lck(mtx); cond.wait(lck, [this]{return !q.empty(); }); val = q.front(); q.pop_front(); } //------------------------------------------------ // thread function that starts up and gets its identifier and then // waits for a promise to be filled by some other thread. void func(std::promise<int> &jj) { int myId = std::atomic_fetch_add(&kount, 1); // get my identifier std::future<int> intFuture(jj.get_future()); auto ll = intFuture.get(); // wait for the promise attached to the future std::cout << " func " << myId << " future " << ll << std::endl; } // function takes a promise from one thread and creates a value to provide as a promise to another thread. void func2(std::promise<int> &jj, std::promise<int>&pp) { int myId = std::atomic_fetch_add(&kount, 1); // get my identifier std::future<int> intFuture(jj.get_future()); auto ll = intFuture.get(); // wait for the promise attached to the future auto promiseValue = ll * 100; // create the value to provide as promised to the next thread in the chain pp.set_value(promiseValue); std::cout << " func2 " << myId << " promised " << promiseValue << " ll was " << ll << std::endl; } // thread function that starts up and waits for a series of notifications for work to do. void func3(Sync_queue<int> &q, int iBegin, int iEnd, int *pInts) { int myId = std::atomic_fetch_add(&kount, 1); int ll; q.get(ll); // wait on a notification and when we get it, processes it. while (ll > 0) { std::cout << " func3 " << myId << " start loop base " << ll << " " << iBegin << " to " << iEnd << std::endl; for (int i = iBegin; i < iEnd; i++) { pInts[i] = ll + i; } q.get(ll); // we finished this job so now wait for the next one. } } int _tmain(int argc, _TCHAR* argv[]) { std::chrono::milliseconds myDur(1000); // create our various promise and future objects which we are going to use to synchronise our threads // create our three threads which are going to do some simple things. std::cout << "MAIN #1 - create our threads." << std::endl; // thread T1 is going to wait on a promised int std::promise<int> intPromiseT1; std::thread t1(func, std::ref(intPromiseT1)); // thread T2 is going to wait on a promised int and then provide a promised int to thread T3 std::promise<int> intPromiseT2; std::promise<int> intPromiseT3; std::thread t2(func2, std::ref(intPromiseT2), std::ref(intPromiseT3)); // thread T3 is going to wait on a promised int and then provide a promised int to thread Main std::promise<int> intPromiseMain; std::thread t3(func2, std::ref(intPromiseT3), std::ref(intPromiseMain)); std::this_thread::sleep_for(myDur); std::cout << "MAIN #2 - provide the value for promise #1" << std::endl; intPromiseT1.set_value(22); std::this_thread::sleep_for(myDur); std::cout << "MAIN #2.2 - provide the value for promise #2" << std::endl; std::this_thread::sleep_for(myDur); intPromiseT2.set_value(1001); std::this_thread::sleep_for(myDur); std::cout << "MAIN #2.4 - set_value 1001 completed." << std::endl; std::future<int> intFutureMain(intPromiseMain.get_future()); auto t3Promised = intFutureMain.get(); std::cout << "MAIN #2.3 - intFutureMain.get() from T3. " << t3Promised << std::endl; t1.join(); t2.join(); t3.join(); int iArray[100]; Sync_queue<int> q1; // notification queue for messages to thread t11 Sync_queue<int> q2; // notification queue for messages to thread t12 std::thread t11(func3, std::ref(q1), 0, 5, iArray); // start thread t11 with its queue and section of the array std::this_thread::sleep_for(myDur); std::thread t12(func3, std::ref(q2), 10, 15, iArray); // start thread t12 with its queue and section of the array std::this_thread::sleep_for(myDur); // send a series of jobs to our threads by sending notification to each thread's queue. for (int i = 0; i < 5; i++) { std::cout << "MAIN #11 Loop to do array " << i << std::endl; std::this_thread::sleep_for(myDur); // sleep a moment for I/O to complete q1.put(i + 100); std::this_thread::sleep_for(myDur); // sleep a moment for I/O to complete q2.put(i + 1000); std::this_thread::sleep_for(myDur); // sleep a moment for I/O to complete } // close down the job threads so that we can quit. q1.put(-1); // indicate we are done with agreed upon out of range data value q2.put(-1); // indicate we are done with agreed upon out of range data value t11.join(); t12.join(); return 0; }
это простое приложение создает следующий вывод.
MAIN #1 - create our threads. MAIN #2 - provide the value for promise #1 func 1 future 22 MAIN #2.2 - provide the value for promise #2 func2 2 promised 100100 ll was 1001 func2 3 promised 10010000 ll was 100100 MAIN #2.4 - set_value 1001 completed. MAIN #2.3 - intFutureMain.get() from T3. 10010000 MAIN #11 Loop to do array 0 func3 4 start loop base 100 0 to 5 func3 5 start loop base 1000 10 to 15 MAIN #11 Loop to do array 1 func3 4 start loop base 101 0 to 5 func3 5 start loop base 1001 10 to 15 MAIN #11 Loop to do array 2 func3 4 start loop base 102 0 to 5 func3 5 start loop base 1002 10 to 15 MAIN #11 Loop to do array 3 func3 4 start loop base 103 0 to 5 func3 5 start loop base 1003 10 to 15 MAIN #11 Loop to do array 4 func3 4 start loop base 104 0 to 5 func3 5 start loop base 1004 10 to 15
обещание-это другой конец провода.
представьте, что вам нужно получить значение
future
вычисляется с помощьюasync
. Однако вы не хотите, чтобы он вычислялся в том же потоке, и вы даже не создаете поток "сейчас" - возможно, ваше программное обеспечение было разработано для выбора потока из пула, поэтому вы не знаете кто выполнит вычисление ЧЕ в конце.теперь, что вы переходите к этому (пока неизвестно) поток / класс / сущность? Вы не проходите
future
, поскольку результат. Вы хотите передать что-то, что подключено доfuture
что составляет другой конец провода, так что вы просто спроситеfuture
без знания о том, кто на самом деле будет вычислять/писать что-то.это
promise
. Это дескриптор подключен к вашейfuture
. Еслиfuture
Это динамик, и сget()
вы начинаете слушать, пока какой-то звук не выходит,promise
это микрофон; но не просто микрофон, это the микрофон подключен одним проводом к динамику, который вы держите. Вы можете знать, кто находится на другом конце, но вам не нужно это знать - вы просто даете это и ждете, пока другая сторона не скажет что-то.