Что такое STD :: обещание?

384

Я достаточно знаком с C ++ 11 -х годов std::thread, std::asyncи std::futureкомпоненты (например , см этот ответ ), которые являются прямо вперед.

Тем не менее, я не могу понять, что std::promiseесть, что он делает и в каких ситуациях его лучше всего использовать. Сам стандартный документ не содержит много информации, кроме краткого описания его класса, и не содержит просто :: thread .

Может ли кто-нибудь дать краткий, краткий пример ситуации, когда std::promiseнужен и где это самое идиоматическое решение?

Керрек С.Б.
источник
2
Вот некоторый код с этим в: en.cppreference.com/w/cpp/thread/future
Крис
58
Действительно, очень короткая версия: std::promiseоткуда std::futureвзялись. std::futureэто то, что позволяет вам получить обещанное вам значение. Когда вы обращаетесь get()к будущему, он ждет, пока владелец std::promiseне установит значение (вызывая set_valueобещание). Если обещание будет уничтожено до того, как будет установлено значение, а затем вы вызовете get()будущее, связанное с этим обещанием, вы получите std::broken_promiseисключение, потому что вам было обещано значение, но получить его невозможно.
Джеймс МакНеллис
14
Я рекомендую, если вы можете / хотите, посмотрите на C ++ параллельности в действии по Энтони Уильямс
Дэвид Родригес - dribeas
32
@KerrekSB std::broken_promise- лучший именованный идентификатор в стандартной библиотеке. И нет std::atomic_future.
Cubbi
3
Downvoter, хочешь объяснить свое возражение?
Керрек С.Б.

Ответы:

182

В словах [futures.state] a std::futureявляется асинхронным возвращаемым объектом («объект, который читает результаты из общего состояния»), а a std::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. Например, вы можете иметь массив из нескольких promises и связанных futures и иметь один поток, который выполняет несколько вычислений и устанавливает результат для каждого обещания. asyncпозволит вам вернуть только один результат, чтобы вернуть несколько, вам нужно будет позвонить asyncнесколько раз, что может тратить ресурсы.

Джонатан Уэйкли
источник
10
Может ли тратить ресурсы? Может быть неправильно, если этот код не может быть распараллелен.
Щенок
«асинхронный возврат» и «результат чтения из общего состояния» в основном ортогональны, что делает первое предложение немного запутанным. Вы хотите сказать, что разделение государства - это будущее и обещание? Если это так, скажите, пожалуйста, с самого начала.
einpoklum
@einpoklum почему вы перестали читать «асинхронный объект возврата» перед последним словом? Я цитирую терминологию стандарта. A futureявляется конкретным примером асинхронного объекта возврата , который является объектом, который считывает результат, который был возвращен асинхронно, через общее состояние. A promiseявляется конкретным примером асинхронного поставщика , который представляет собой объект, который записывает значение в общее состояние, которое может быть прочитано асинхронно. Я имел в виду то, что я написал.
Джонатан
496

Теперь я немного лучше понимаю ситуацию (в немалой степени из-за ответов здесь!), Поэтому я подумал, что добавлю немного своих собственных заметок.


В C ++ 11 есть два различных, хотя и связанных между собой понятия: асинхронное вычисление (функция, которая вызывается где-то еще) и параллельное выполнение ( поток , то, что работает одновременно). Это несколько ортогональные понятия. Асинхронные вычисления - это просто другая разновидность вызова функции, а поток - это контекст выполнения. Потоки полезны сами по себе, но для целей этого обсуждения я буду рассматривать их как детали реализации.


Для асинхронных вычислений существует иерархия абстракций. Например, предположим, у нас есть функция, которая принимает несколько аргументов:

int foo(double, char, bool);

Прежде всего, у нас есть шаблон std::future<T>, который представляет будущее значение типа T. Значение может быть получено через функцию-член get(), которая эффективно синхронизирует программу, ожидая результата. В качестве альтернативы поддерживается будущее wait_for(), которое можно использовать для проверки того, доступен ли уже результат. Фьючерсы следует рассматривать как асинхронную замену для обычных возвращаемых типов. Для нашего примера функции мы ожидаем std::future<int>.

Теперь перейдем к иерархии, от высшего к низшему уровню:

  1. 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
  2. Теперь мы можем рассмотреть, как реализовать что-то подобное 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
  3. Теперь мы дошли до самого низкого уровня: как бы мы реализовали упакованную задачу? Вот где 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. Обратите внимание, что перемещение всей задачи включает перемещение обещания. В более специализированных ситуациях можно также явно переместить объект обещания в новый поток и сделать его аргументом функции функции потока, но оболочка задачи, подобная приведенной выше, кажется более гибким и менее навязчивым решением.


Делать исключения

Обещания тесно связаны с исключениями. Одного интерфейса обещания недостаточно, чтобы полностью передать его состояние, поэтому создаются исключения, когда операция с обещанием не имеет смысла. Все исключения имеют тип 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();
}
Керрек С.Б.
источник
Вы сказали: «... который эффективно синхронизирует программу, ожидая результата». , Что значит «синхронизировать» здесь? Что означает все утверждение? Я не могу этого понять. Ни одно из значений слова «синхронизировать» из этой словарной статьи не помогает мне понять предложение. Означает ли просто «ожидание» «синхронизация»? Синхронизируется ли каждое ожидание? Я думаю, что частично понимаю, что вы имеете в виду, но я не уверен, что вы на самом деле имеете в виду.
Наваз
9
Хороший ответ, спасибо за вашу помощь. Что касается части std :: async, я помню, что мы могли определить, будет ли он порождать другой поток или работать синхронно с флагом (std :: launch :: async, std :: launch :: deferred)
StereoMatching
1
@FelixDombek: Идеальная пересылка и т. Д. std::functionИмеет много конструкторов; нет причин не подвергать их потребителю my_task.
Керрек С.Б.
1
@DaveedV .: Спасибо за отзыв! Да, это тестовый пример 7. Если вы уничтожаете обещание без установки значения или исключения, то вызов get()на будущее вызывает исключение. Я поясню это, добавив «прежде чем он будет уничтожен»; пожалуйста, дайте мне знать, если это достаточно ясно.
Kerrek SB
3
Наконец-то got()моя futureработа с библиотекой поддержки потоков по promiseвашему удивительному объяснению!
солнечная луна
33

Бартош Милевски обеспечивает хорошую рецензию.

C ++ разбивает реализацию фьючерсов на множество небольших блоков

STD :: обещание является одной из этих частей.

Обещание - это средство для передачи возвращаемого значения (или исключения) из потока, выполняющего функцию, в поток, который получает деньги в будущем функции.

...

Будущее - это объект синхронизации, построенный вокруг принимающей стороны канала обещания.

Итак, если вы хотите использовать будущее, вы получите обещание, которое вы используете, чтобы получить результат асинхронной обработки.

Пример со страницы:

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
Пол Рубел
источник
4
Увидев обещание в конструкторе потока, наконец-то уронил пенни. Статья Бартоша, возможно, не самая лучшая, но она объясняет, как элементы связаны друг с другом. Спасибо.
Kerrek SB
28

В грубом приближении вы можете рассматривать std::promiseкак другой конец std::future(это неверно , но для иллюстрации вы можете думать, как будто это было). Конец потребителя канала связи будет использовать a std::futureдля получения данных из общего состояния, в то время как поток-производитель будет использовать a std::promiseдля записи в общее состояние.

Дэвид Родригес - дрибеи
источник
12
@KerrekSB: std::asyncконцептуально (это не предусмотрено стандартом) понимается как функция, которая создает std::promise, помещает это в пул потоков (в некотором роде, может быть пулом потоков, может быть новым потоком, ...) и возвращает связанный std::futureс абонентом. На стороне клиента вы ожидаете, std::futureа поток на другом конце вычисляет результат и сохраняет его в std::promise. Примечание: стандарт требует общего состояния и, std::futureно не наличия, std::promiseв данном конкретном случае использования.
Дэвид Родригес - dribeas
6
@KerrekSB: std::futureне будет вызывать joinпоток, у него есть указатель на общее состояние, которое является фактическим коммуникационным буфером. Общее состояние имеет механизм синхронизации (вероятно, std::function+ std::condition_variableдля блокировки вызывающей стороны до тех пор, пока не std::promiseбудет выполнено. Выполнение потока ортогонально всему этому, и во многих реализациях вы можете обнаружить, что std::asyncони не выполняются новыми потоками, которые затем присоединяются, но скорее пулом потоков, чье время жизни продолжается до конца программы.
Дэвид Родригес - dribeas
1
@ DavidRodríguez-dribeas: пожалуйста, отредактируйте информацию из комментариев в свой ответ.
Марк Мутц - mmutz
2
@JonathanWakely: Это не означает, что он должен выполняться в новом потоке, только то, что он должен выполняться асинхронно, как если бы он был запущен во вновь созданном потоке. Основным преимуществом std::asyncявляется то, что библиотека времени выполнения может принимать правильные решения в отношении количества создаваемых потоков, и в большинстве случаев я ожидаю, что среды выполнения используют пулы потоков. В настоящее время VS2012 использует пул потоков под капотом, и он не нарушает правило « как будто» . Обратите внимание, что существует очень мало гарантий, которые должны быть выполнены для этого конкретного как-будто .
Дэвид Родригес - dribeas
1
Локальные локальные потоки должны быть повторно инициализированы, но правило «как будто» разрешает что-либо (вот почему я
пишу
11

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

KJP
источник
8

В асинхронной обработке действительно есть три основных объекта. C ++ 11 в настоящее время фокусируется на 2 из них.

Основные вещи, которые вам нужны для асинхронной работы логики:

  1. Задача (логика упаковывается как некоторый функтор объект) , который будет работать «где - то».
  2. Фактический обрабатывающий узел - нить, процесс и т.д. , которые RUNS таких функторов , когда они предоставляются к нему. Посмотрите на шаблон проектирования «Command», чтобы понять, как это делает базовый пул рабочих потоков.
  3. Дескриптор результата : кому-то нужен этот результат, и ему нужен объект, который ПОЛУЧИТ его для них. По ООП и другим причинам любое ожидание или синхронизацию следует выполнять в API этого дескриптора.

C ++ 11 называет то, о чем я говорю, в (1) std::promiseи то, что в (3) std::future. std::threadявляется единственной вещью, предоставленной публично для (2). Это прискорбно, потому что реальным программам необходимо управлять потоками и ресурсами памяти, и большинству нужно, чтобы задачи выполнялись в пулах потоков, а не создавали и уничтожали поток для каждой маленькой задачи (что почти всегда само по себе вызывает ненужные потери производительности и может легко создать ресурс голод еще что хуже).

Согласно Хербу Саттеру и другим специалистам по мозговому доверию C ++ 11, существуют предварительные планы по добавлению того, std::executorчто, как и в Java, послужит основой для пулов потоков и логически аналогичных установок для (2). Может быть, мы увидим это в C ++ 2014, но моя ставка больше похожа на C ++ 17 (и Бог поможет нам, если они испортят стандарт для них).

Зак Йезек
источник
7

A std::promiseсоздается как конечная точка для пары обещание / будущее, а другая std::future(созданная из std :: обещание с использованием get_future()метода) является другой конечной точкой. Это простой метод, позволяющий синхронизировать два потока, поскольку один поток передает данные другому потоку через сообщение.

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

Механизм обещания / будущего - это только одно направление: от потока, который использует set_value()метод a, std::promiseк потоку, который использует get()a std::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::futurewith co_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-е издание".

// 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
Ричард Чемберс
источник
1

Обещание - это другой конец провода.

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

Теперь, что вы передаете этому (пока неизвестному) потоку / классу / сущности? Вы не пропустите future, так как это результат . Вы хотите передать что-то, что связано с futureи которое представляет другой конец провода , поэтому вы просто запросите futureбез знания о том, кто на самом деле что-то вычислит / напишет.

Это promise. Это ручка, связанная с вашим future. Если futureэто динамик , и get()вы начинаете слушать, пока не раздастся какой-то звук, promiseэто микрофон ; но не просто микрофон, это микрофон , подключенный с помощью одного провода к динамику вы держите. Вы можете знать, кто на другом конце, но вам не нужно это знать - вы просто даете это и ждете, пока другая сторона что-то скажет.

Narcolessico
источник
0

http://www.cplusplus.com/reference/future/promise/

Объяснение одного предложения: furture :: get () ждет promse :: set_value () навсегда.

void print_int(std::future<int>& fut) {
    int x = fut.get(); // future would wait prom.set_value forever
    std::cout << "value: " << x << '\n';
}

int main()
{
    std::promise<int> prom;                      // create promise

    std::future<int> fut = prom.get_future();    // engagement with future

    std::thread th1(print_int, std::ref(fut));  // send future to new thread

    prom.set_value(10);                         // fulfill promise
                                                 // (synchronizes with getting the future)
    th1.join();
    return 0;
}
Zhang
источник