Я достаточно знаком с C ++ 11 -х годов std::thread
, std::async
и std::future
компоненты (например , см этот ответ ), которые являются прямо вперед.
Тем не менее, я не могу понять, что std::promise
есть, что он делает и в каких ситуациях его лучше всего использовать. Сам стандартный документ не содержит много информации, кроме краткого описания его класса, и не содержит просто :: thread .
Может ли кто-нибудь дать краткий, краткий пример ситуации, когда std::promise
нужен и где это самое идиоматическое решение?
c++
multithreading
c++11
promise
standard-library
Керрек С.Б.
источник
источник
std::promise
откудаstd::future
взялись.std::future
это то, что позволяет вам получить обещанное вам значение. Когда вы обращаетесьget()
к будущему, он ждет, пока владелецstd::promise
не установит значение (вызываяset_value
обещание). Если обещание будет уничтожено до того, как будет установлено значение, а затем вы вызоветеget()
будущее, связанное с этим обещанием, вы получитеstd::broken_promise
исключение, потому что вам было обещано значение, но получить его невозможно.std::broken_promise
- лучший именованный идентификатор в стандартной библиотеке. И нетstd::atomic_future
.Ответы:
В словах [futures.state] a
std::future
является асинхронным возвращаемым объектом («объект, который читает результаты из общего состояния»), а astd::promise
является асинхронным поставщиком («объект, который предоставляет результат в общее состояние»), то есть Обещание - это то, на что вы устанавливаете результат, чтобы вы могли получить его из связанного будущего.Асинхронный поставщик - это то, что изначально создает общее состояние, к которому относится будущее.
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
несколько раз, что может тратить ресурсы.источник
future
является конкретным примером асинхронного объекта возврата , который является объектом, который считывает результат, который был возвращен асинхронно, через общее состояние. Apromise
является конкретным примером асинхронного поставщика , который представляет собой объект, который записывает значение в общее состояние, которое может быть прочитано асинхронно. Я имел в виду то, что я написал.Теперь я немного лучше понимаю ситуацию (в немалой степени из-за ответов здесь!), Поэтому я подумал, что добавлю немного своих собственных заметок.
В C ++ 11 есть два различных, хотя и связанных между собой понятия: асинхронное вычисление (функция, которая вызывается где-то еще) и параллельное выполнение ( поток , то, что работает одновременно). Это несколько ортогональные понятия. Асинхронные вычисления - это просто другая разновидность вызова функции, а поток - это контекст выполнения. Потоки полезны сами по себе, но для целей этого обсуждения я буду рассматривать их как детали реализации.
Для асинхронных вычислений существует иерархия абстракций. Например, предположим, у нас есть функция, которая принимает несколько аргументов:
Прежде всего, у нас есть шаблон
std::future<T>
, который представляет будущее значение типаT
. Значение может быть получено через функцию-членget()
, которая эффективно синхронизирует программу, ожидая результата. В качестве альтернативы поддерживается будущееwait_for()
, которое можно использовать для проверки того, доступен ли уже результат. Фьючерсы следует рассматривать как асинхронную замену для обычных возвращаемых типов. Для нашего примера функции мы ожидаемstd::future<int>
.Теперь перейдем к иерархии, от высшего к низшему уровню:
std::async
: Наиболее удобный и простой способ выполнения асинхронных вычислений - черезasync
шаблон функции, который немедленно возвращает будущее сопоставления:У нас очень мало контроля над деталями. В частности, мы даже не знаем, выполняется ли функция одновременно, последовательно
get()
или какой-либо другой черной магией. Тем не менее, результат легко получить при необходимости:Теперь мы можем рассмотреть, как реализовать что-то подобное
async
, но способом, который мы контролируем. Например, мы можем настаивать на том, чтобы функция выполнялась в отдельном потоке. Мы уже знаем, что можем предоставить отдельный поток с помощьюstd::thread
класса.На следующий более низкий уровень абстракции делает именно это:
std::packaged_task
. Это шаблон, который оборачивает функцию и обеспечивает будущее для возвращаемого значения функции, но сам объект может быть вызван и вызывать его по усмотрению пользователя. Мы можем настроить это так:Будущее становится готовым, как только мы вызовем задачу и вызов завершится. Это идеальная работа для отдельной темы. Нам просто нужно перенести задачу в поток:
Поток начинает работать немедленно. Мы можем или
detach
это, или иметьjoin
его в конце области, или когда угодно (например, используяscoped_thread
обертку Энтони Уильямса , которая действительно должна быть в стандартной библиотеке). Детали использования здесьstd::thread
нас не касаются; просто обязательно присоединяйтесь или отсоединяйтесь вthr
конце концов. Важно то, что всякий раз, когда завершается вызов функции, наш результат готов:Теперь мы дошли до самого низкого уровня: как бы мы реализовали упакованную задачу? Вот где
std::promise
приходит. Обещание - это строительный блок для общения с будущим. Основными шагами являются следующие:Вызывающий поток дает обещание.
Вызывающий поток получает будущее от обещания.
Обещание вместе с аргументами функции перемещаются в отдельный поток.
Новый поток выполняет функцию и выполняет обещание.
Исходный поток извлекает результат.
В качестве примера, вот наше собственное «упакованное задание»:
Использование этого шаблона, по сути, такое же, как и для
std::packaged_task
. Обратите внимание, что перемещение всей задачи включает перемещение обещания. В более специализированных ситуациях можно также явно переместить объект обещания в новый поток и сделать его аргументом функции функции потока, но оболочка задачи, подобная приведенной выше, кажется более гибким и менее навязчивым решением.Делать исключения
Обещания тесно связаны с исключениями. Одного интерфейса обещания недостаточно, чтобы полностью передать его состояние, поэтому создаются исключения, когда операция с обещанием не имеет смысла. Все исключения имеют тип
std::future_error
, который является производным отstd::logic_error
. Прежде всего, описание некоторых ограничений:По умолчанию созданное обещание неактивно. Неактивные обещания могут умереть без последствий.
Обещание становится активным, когда будущее получается через
get_future()
. Тем не менее, только одно будущее может быть получено!Обещание должно либо быть выполнено с помощью,
set_value()
либо должно быть установлено исключениеset_exception()
до того, как истечет срок его службы, если его будущее будет использовано. Удовлетворенное обещание может умереть без последствий иget()
станет доступным в будущем. Обещание с исключением вызовет сохраненное исключение при вызовеget()
в будущем. Если обещание умирает без каких-либо ценностей или исключений, вызовget()
будущего вызовет исключение «нарушенного обещания».Вот небольшая серия тестов, чтобы продемонстрировать эти различные исключительные поведения. Сначала жгут:
Теперь о тестах.
Случай 1: неактивное обещание
Случай 2: активное обещание, неиспользованное
Случай 3: слишком много будущего
Случай 4: удовлетворенное обещание
Случай 5: слишком много удовлетворения
То же самое исключение , если есть более чем один из любой из
set_value
илиset_exception
.Случай 6: Исключение
Случай 7: нарушенное обещание
источник
std::function
Имеет много конструкторов; нет причин не подвергать их потребителюmy_task
.get()
на будущее вызывает исключение. Я поясню это, добавив «прежде чем он будет уничтожен»; пожалуйста, дайте мне знать, если это достаточно ясно.got()
мояfuture
работа с библиотекой поддержки потоков поpromise
вашему удивительному объяснению!Бартош Милевски обеспечивает хорошую рецензию.
STD :: обещание является одной из этих частей.
...
Итак, если вы хотите использовать будущее, вы получите обещание, которое вы используете, чтобы получить результат асинхронной обработки.
Пример со страницы:
источник
В грубом приближении вы можете рассматривать
std::promise
как другой конецstd::future
(это неверно , но для иллюстрации вы можете думать, как будто это было). Конец потребителя канала связи будет использовать astd::future
для получения данных из общего состояния, в то время как поток-производитель будет использовать astd::promise
для записи в общее состояние.источник
std::async
концептуально (это не предусмотрено стандартом) понимается как функция, которая создаетstd::promise
, помещает это в пул потоков (в некотором роде, может быть пулом потоков, может быть новым потоком, ...) и возвращает связанныйstd::future
с абонентом. На стороне клиента вы ожидаете,std::future
а поток на другом конце вычисляет результат и сохраняет его вstd::promise
. Примечание: стандарт требует общего состояния и,std::future
но не наличия,std::promise
в данном конкретном случае использования.std::future
не будет вызыватьjoin
поток, у него есть указатель на общее состояние, которое является фактическим коммуникационным буфером. Общее состояние имеет механизм синхронизации (вероятно,std::function
+std::condition_variable
для блокировки вызывающей стороны до тех пор, пока неstd::promise
будет выполнено. Выполнение потока ортогонально всему этому, и во многих реализациях вы можете обнаружить, чтоstd::async
они не выполняются новыми потоками, которые затем присоединяются, но скорее пулом потоков, чье время жизни продолжается до конца программы.std::async
является то, что библиотека времени выполнения может принимать правильные решения в отношении количества создаваемых потоков, и в большинстве случаев я ожидаю, что среды выполнения используют пулы потоков. В настоящее время VS2012 использует пул потоков под капотом, и он не нарушает правило « как будто» . Обратите внимание, что существует очень мало гарантий, которые должны быть выполнены для этого конкретного как-будто .std::promise
канал или путь для информации, возвращаемой из асинхронной функции.std::future
это механизм синхронизации, который заставляет вызывающуюstd::promise
функцию ждать, пока возвращаемое значение, переданное в, будет готово (то есть его значение установлено внутри функции).источник
В асинхронной обработке действительно есть три основных объекта. C ++ 11 в настоящее время фокусируется на 2 из них.
Основные вещи, которые вам нужны для асинхронной работы логики:
C ++ 11 называет то, о чем я говорю, в (1)
std::promise
и то, что в (3)std::future
.std::thread
является единственной вещью, предоставленной публично для (2). Это прискорбно, потому что реальным программам необходимо управлять потоками и ресурсами памяти, и большинству нужно, чтобы задачи выполнялись в пулах потоков, а не создавали и уничтожали поток для каждой маленькой задачи (что почти всегда само по себе вызывает ненужные потери производительности и может легко создать ресурс голод еще что хуже).Согласно Хербу Саттеру и другим специалистам по мозговому доверию C ++ 11, существуют предварительные планы по добавлению того,
std::executor
что, как и в Java, послужит основой для пулов потоков и логически аналогичных установок для (2). Может быть, мы увидим это в C ++ 2014, но моя ставка больше похожа на C ++ 17 (и Бог поможет нам, если они испортят стандарт для них).источник
A
std::promise
создается как конечная точка для пары обещание / будущее, а другаяstd::future
(созданная из std :: обещание с использованиемget_future()
метода) является другой конечной точкой. Это простой метод, позволяющий синхронизировать два потока, поскольку один поток передает данные другому потоку через сообщение.Вы можете думать об этом как о том, что один поток создает обещание для предоставления данных, а другой поток собирает обещание в будущем. Этот механизм может быть использован только один раз.
Механизм обещания / будущего - это только одно направление: от потока, который использует
set_value()
метод a,std::promise
к потоку, который используетget()
astd::future
для получения данных. Исключение генерируется, еслиget()
метод будущего вызывается более одного раза.Если поток с
std::promise
не использовалset_value()
для выполнения своего обещания, то когда второй поток вызываетget()
егоstd::future
для сбора обещания, второй поток перейдет в состояние ожидания, пока обещание не будет выполнено первым потоком,std::promise
когда он используетset_value()
метод отправить данные.Благодаря предложенным сопрограммам Технической спецификации N4663 Языки программирования - расширения C ++ для сопрограмм и поддержке компилятора Visual Studio 2017 C ++
co_await
также возможно использоватьstd::future
иstd::async
писать функциональные возможности сопрограмм. См. Обсуждение и пример в https://stackoverflow.com/a/50753040/1466970, в котором есть один раздел, в котором обсуждается использованиеstd::future
withco_await
.В следующем примере кода, простом консольном приложении Visual Studio 2013 для Windows, показано использование нескольких классов / шаблонов параллелизма C ++ 11 и других функций. Это иллюстрирует использование для обещания / будущего, которое работает хорошо, автономные потоки, которые будут выполнять некоторую задачу и остановку, и использование, где требуется более синхронное поведение и из-за необходимости множественных уведомлений, пара обещание / будущее не работает.
Одно замечание об этом примере - задержки, добавленные в разных местах. Эти задержки были добавлены только для того, чтобы гарантировать, что различные сообщения, распечатанные на консоли
std::cout
, будут четкими и что текст из нескольких потоков не будет смешиваться.Первая часть
main()
- создание трех дополнительных потоков и использованиеstd::promise
иstd::future
для отправки данных между потоками. Интересным моментом является то, что основной поток запускает поток T2, который будет ожидать данные из основного потока, что-то делать, а затем отправлять данные в третий поток T3, который затем что-то будет делать и отправлять данные обратно в Основная тема.Вторая часть
main()
создает два потока и набор очередей, чтобы разрешить несколько сообщений из основного потока каждому из двух созданных потоков. Мы не можем использоватьstd::promise
иstd::future
для этого, потому что обещание / будущий дуэт являются одним выстрелом и не могут быть использованы повторно.Источник для класса
Sync_queue
взят из Страуструпа "Язык программирования C ++: 4-е издание".Это простое приложение создает следующий вывод.
источник
Обещание - это другой конец провода.
Представьте, что вам нужно получить значение
future
существа, вычисленного с помощьюasync
. Однако вы не хотите, чтобы он вычислялся в одном и том же потоке, и вы даже не создаете поток «сейчас» - возможно, ваше программное обеспечение было разработано для выбора потока из пула, поэтому вы не знаете, кто будет выполнить че вычисление в конце.Теперь, что вы передаете этому (пока неизвестному) потоку / классу / сущности? Вы не пропустите
future
, так как это результат . Вы хотите передать что-то, что связано сfuture
и которое представляет другой конец провода , поэтому вы просто запроситеfuture
без знания о том, кто на самом деле что-то вычислит / напишет.Это
promise
. Это ручка, связанная с вашимfuture
. Еслиfuture
это динамик , иget()
вы начинаете слушать, пока не раздастся какой-то звук,promise
это микрофон ; но не просто микрофон, это микрофон , подключенный с помощью одного провода к динамику вы держите. Вы можете знать, кто на другом конце, но вам не нужно это знать - вы просто даете это и ждете, пока другая сторона что-то скажет.источник
http://www.cplusplus.com/reference/future/promise/
Объяснение одного предложения: furture :: get () ждет promse :: set_value () навсегда.
источник