Що таке std :: обіцянка?


384

Я досить знайомий з C ++ 11 std::thread, std::asyncіstd::future компоненти (наприклад , див цей відповідь ), які є прямо вперед.

Однак я не можу повністю зрозуміти, що std::promiseтаке, що робить і в яких ситуаціях найкраще використовувати. Сам стандартний документ не містить у собі великої кількості інформації поза конспектом класу, а також не містить просто :: thread .

Чи може хтось, будь ласка, навести короткий, стислий приклад ситуації, коли std::promiseпотрібна ситуація, і де це є найбільш ідіоматичним рішенням?


2
Ось код з ним у: en.cppreference.com/w/cpp/thread/future
chris

58
Справді, дуже коротка версія така: std::promiseзвідки std::futureпоходить. std::futureце те, що дозволяє отримати цінність, яку вам обіцяли . Коли ви закликаєте get()майбутнє, він чекає, поки власник тієї, std::promiseз якою він встановлює значення (закликаючи set_valueобіцянку). Якщо обіцянку буде знищено до встановлення значення, а потім ви будете закликати get()майбутнє, пов’язане з цією обіцянкою, ви отримаєте std::broken_promiseвиняток, оскільки вам обіцяли значення, але отримати його неможливо.
James McNellis

14
Рекомендую, якщо ви можете / хочете, подивитись на C ++ Concurrency in Action by Ентоні Вільямс
Девід Родрігес - 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і a std::promise) та a, std::threadале це безпечніше і простіше у використанні std::async.

std::promiseє трохи нижчим рівнем, тому що, коли ви хочете передати асинхронний результат у майбутнє, але код, який готує результат, не може бути перетворений в одну функцію, придатну для переходу до std::async. Наприклад, у вас може бути масив з декількох promises та пов'язаних futures і мати один потік, який робить кілька обчислень і встановлює результат на кожну обіцянку. asyncдозволить лише повернути один результат, повернути кілька, вам потрібно було б зателефонувати asyncкілька разів, що може витратити ресурси.


10
Можливо, відпрацьовані ресурси? Можливо, це неправильно, якщо цей код не можна паралелізувати.
Щеня

"асинхронне повернення" та "зчитування результатів із спільного стану" здебільшого є ортогональними, що робить перше речення трохи заплутаним. Ви хочете сказати, що розподіл держави - це між майбутнім та обіцянкою? Якщо так, будь ласка, скажіть це прямо з самого початку.
einpoklum

@einpoklum чому ти перестав читати "асинхронний об'єкт повернення" перед останнім словом? Я цитую термінологію стандарту. A future- конкретний приклад об'єкта асинхронного повернення , який є об'єктом, який читає результат, який був повернутий асинхронно, через загальний стан. A promise- конкретний приклад асинхронного провайдера , який є об'єктом, який записує значення у загальний стан, який може читатися асинхронно. Я мав на увазі те, що написав.
Jonathan Wakely

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 :: запуск :: async, std :: запуск :: відкладено)
StereoMatching

1
@FelixDombek: ідеальне переадресація тощо std::functionмає багато конструкторів; немає причин не піддавати їх споживачу my_task.
Керрек СБ

1
@DaveedV .: Дякую за відгук! Так, це тестовий випадок 7: Якщо ви знищите обіцянку, не встановлюючи ані значення, ані винятку, тоді заклик get()у майбутньому викликає виняток. Я уточню це, додавши «до того, як воно буде знищене»; будь ласка, дайте мені знати, якщо це достатньо зрозуміло.
Керрек СБ

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
Побачивши обіцянку в конструкторі нитки, нарешті зробило краплі копійки. Стаття Бартоша, можливо, не найкраща, але вона пояснює, як елементи з’єднуються між собою. Дякую.
Керрек СБ

28

У приблизному наближенні ви можете розглядати std::promiseяк інший кінець std::future(це помилково , але для ілюстрації ви можете подумати, ніби це було). Споживчий кінець каналу зв'язку використовував би a std::futureдля споживання даних із загального стану, тоді як виробник використовує a std::promiseдля запису в загальний стан.


12
@KerrekSB: std::asyncможе концептуально (це не передбачено стандартом) розуміється як функція, яка створює a std::promise, штовхає його в пул потоків (різновиди, може бути пул потоків, може бути новим потоком, ...) і повертається пов'язаний std::futureз абонентом. На стороні клієнта ви будете чекати на, std::futureа нитка на іншому кінці буде обчислювати результат і зберігати його в std::promise. Примітка: стандарт вимагає загального стану та, std::futureале не існування, std::promiseу цьому конкретному випадку використання.
Девід Родрігес - дрибес

6
@KerrekSB: std::futureне буде викликати joinпотік, він має вказівник на загальний стан, який є фактичним буфером зв'язку. Загального стану має механізм синхронізації (можливо std::function+ , std::condition_variableщоб заблокувати абонента , поки std::promiseне буде виконано. Виконання нитки ортогонален всім цим, і в багатьох реалізаціях ви можете виявити , що std::asyncне виконуються новими нитками, які потім приєдналися, але скоріше пулом ниток, термін служби якого триває до кінця програми.
Девід Родрігес - дрибе

1
@ DavidRodríguez-dribeas: будь ласка, відредагуйте інформацію з коментарів у свою відповідь.
Марк Муц - mmutz

2
@JonathanWakely: Це не означає, що він повинен бути виконаний у новому потоці, лише те, що він повинен бути виконаний асинхронно, як якщо б він був запущений у новоствореній темі. Основна перевага std::asyncполягає в тому, що бібліотека часу виконання може приймати правильні рішення щодо кількості створених потоків, і я в більшості випадків очікую виконання, які використовують пули потоків. В даний час VS2012 використовує пул потоків під кришкою, і це не порушує правило як-якщо . Зауважте, що дуже мало гарантій, які потрібно виконати для цього конкретного начебто .
Девід Родрігес - дрибес

1
Місцеві теми для тематики потрібно повторно ініціалізувати, але правило ніби дозволяє щось (саме тому я проставляю курсивом "як би" :)
Джонатан Уейклі

11

std::promise- це канал або шлях для повернення інформації з функції асинхронізації. std::futureце механізм синхронізації, який змушує абонента чекати, поки повернене значення, проведене в std::promiseзначенні, готове (тобто його значення встановлюється всередині функції).


8

В асинхронній обробці дійсно є три основні об'єкти. В даний час C ++ 11 зосереджується на 2 з них.

Основними речами, необхідними для асинхронного запуску певної логіки, є:

  1. Завдання (логіка упаковується як деякий функтор об'єкт) , який буде працювати «де - то».
  2. Фактичний обробляє вузол - нитка, процес і т.д. , які RUNS таких функторів , коли вони надаються до нього. Подивіться на шаблон дизайну "Command", щоб добре уявити, як це робить базовий пул потоків робочих.
  3. Обробка результату : комусь потрібен цей результат, і йому потрібен об'єкт, який отримає його для них. З точки зору OOP та інших причин будь-яке очікування чи синхронізація слід здійснювати в 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 ++ для Coroutines та підтримкою компілятора Visual Studio 2017 C ++ co_await, можливо також використовувати std::futureта std::asyncзаписати функціональність програми. Дивіться обговорення і приклад в https://stackoverflow.com/a/50753040/1466970 , який має в якості однієї секції, присвяченій використанню std::futureз co_await.

Наступний приклад коду, простий додаток консолі Windows Visual Studio 2013, показує використання декількох класів / шаблонів паралелей C ++ 11 та інших функціональних можливостей. Це ілюструє використання обіцянки / майбутнього, яке добре працює, автономні нитки, які виконають певну задачу і зупиняються, і використання, коли потрібна більш синхронність поведінки і через необхідність декількох повідомлень, обіцянка / майбутня пара не працює.

Одне зауваження щодо цього прикладу - затримки, що додаються в різних місцях. Ці затримки додавались лише для того, щоб різні повідомлення, надруковані на консолі, std::coutбули зрозумілими, а текст із кількох потоків не змішався.

Перша частина програми - main()це створення трьох додаткових потоків та використання std::promiseта std::futureпередача даних між потоками. Цікавим моментом є те, коли головний потік запускає потік, T2, який буде чекати даних з головної нитки, робити щось, а потім надсилати дані в третій потік, T3, який потім щось зробить і поверне дані назад до головна нитка.

Друга частина main()створює два потоки та набір черг, щоб дозволити декілька повідомлень від основного потоку до кожного із двох створених потоків. Ми не можемо використовувати std::promiseі std::futureдля цього, тому що дует / майбутній дует - це один кадр і не може бути використаний повторно.

Джерело для класу Sync_queue- із мови програмування на C ++ Stroustrup: 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є мікрофон ; але не просто мікрофон, це мікрофон , підключений за допомогою одного проводу до динаміка ви тримаєте. Ви можете знати, хто на іншому кінці, але вам це не потрібно знати - ви просто даєте це і чекаєте, поки інша сторона щось скаже.


0

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

Пояснення в одному реченні: furture :: get () чекає promise :: 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;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.