464 Stimmen

Was ist std::promise?

Ich bin ziemlich vertraut mit C++11's std::thread , std::async et std::future Komponenten (siehe z.B. diese Antwort ), die einfach zu handhaben sind.

Ich kann jedoch nicht ganz nachvollziehen, was std::promise ist, was es bewirkt und in welchen Situationen es am besten eingesetzt werden kann. Das Standarddokument selbst enthält außer der Klassenzusammenfassung nicht viele Informationen, und auch die std::thread .

Könnte jemand bitte ein kurzes, prägnantes Beispiel für eine Situation nennen, in der ein std::promise benötigt wird und wo es die sinnvollste Lösung ist?

215voto

Jonathan Wakely Punkte 159417

In den Worten von [futures.state] ein std::future ist ein asynchrones Rückgabeobjekt ("ein Objekt, das Ergebnisse aus einem gemeinsamen Zustand liest") und ein std::promise ist ein asynchroner Anbieter ("ein Objekt, das ein Ergebnis für einen gemeinsamen Zustand liefert"), d.h. ein Versprechen ist das, was Sie einstellen. ein Ergebnis an, so dass Sie erhalten. es aus der damit verbundenen Zukunft.

Der asynchrone Anbieter erstellt zunächst den gemeinsamen Zustand, auf den sich ein Future bezieht. std::promise ist ein Typ eines asynchronen Anbieters, std::packaged_task ist eine andere, und das interne Detail von std::async ist eine andere. Jedes dieser Elemente kann einen gemeinsamen Zustand schaffen und Ihnen eine std::future der diesen Zustand teilt, und kann den Zustand bereit machen.

std::async ist ein übergeordnetes Dienstprogramm, das Ihnen ein asynchrones Ergebnisobjekt liefert und sich intern um die Erstellung des asynchronen Anbieters und die Bereitstellung des gemeinsamen Zustands bei Beendigung der Aufgabe kümmert. Man könnte es mit einer std::packaged_task (oder std::bind und eine std::promise ) und eine std::thread aber es ist sicherer und einfacher zu benutzen std::async .

std::promise ist etwas einfacher, wenn Sie ein asynchrones Ergebnis an die Zukunft übergeben wollen, aber der Code, der das Ergebnis bereitstellt, kann nicht in einer einzigen Funktion verpackt werden, die zur Übergabe an std::async . Sie könnten zum Beispiel ein Array mit mehreren promise s und zugehörige future s und haben einen einzigen Thread, der mehrere Berechnungen durchführt und für jedes Versprechen ein Ergebnis festlegt. async nur ein einziges Ergebnis zurückgeben würde, müssten Sie für mehrere Ergebnisse async mehrmals, was zu einer Verschwendung von Ressourcen führen kann.

40voto

Paul Rubel Punkte 25730

Bartosz Milewski bietet eine gute Zusammenfassung.

C++ unterteilt die Implementierung von Futures in eine Reihe von von kleinen Blöcken

std::promise ist einer dieser Teile.

Ein Versprechen ist ein Vehikel zur Übergabe des Rückgabewerts (oder einer Ausnahme) von dem Thread, der eine Funktion ausführt, an den Thread der die Zukunft der Funktion einlöst.

...

Ein future ist das Synchronisationsobjekt, das um die Empfangsende des Versprechenskanals aufgebaut ist.

Wenn Sie also eine Zukunft verwenden möchten, erhalten Sie ein Versprechen, das Sie verwenden, um das Ergebnis der asynchronen Verarbeitung zu erhalten.

Ein Beispiel auf der Seite ist:

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

33voto

In einer groben Annäherung kann man Folgendes berücksichtigen std::promise als das andere Ende einer std::future (dies ist falsch aber zur Veranschaulichung können Sie so tun, als wäre es so). Auf der Verbraucherseite des Kommunikationskanals würde ein std::future um die Daten aus dem gemeinsamen Zustand zu konsumieren, während der Producer-Thread eine std::promise um in den gemeinsamen Zustand zu schreiben.

15voto

kjp Punkte 3046

std::promise ist der Kanal oder Pfad für Informationen, die von der asynchronen Funktion zurückgegeben werden. std::future ist der Synchronisationsmechanismus, der den Aufrufer warten lässt, bis der Rückgabewert, der in der std::promise bereit ist (d. h. sein Wert wird innerhalb der Funktion festgelegt).

10voto

Richard Chambers Punkte 15587

A std::promise wird als Endpunkt für ein Versprechen/Zukunftspaar erstellt und die std::future (erstellt aus dem std::promise unter Verwendung der get_future() Methode) ist der andere Endpunkt. Dies ist eine einfache, einmalige Methode, mit der zwei Threads synchronisiert werden können, da ein Thread einem anderen Thread Daten über eine Nachricht zur Verfügung stellt.

Man kann sich das so vorstellen, dass ein Thread ein Versprechen zur Bereitstellung von Daten erstellt und der andere Thread das Versprechen in der Zukunft einholt. Dieser Mechanismus kann nur einmal verwendet werden.

Der Promise/Future-Mechanismus geht nur in eine Richtung, nämlich von dem Thread, der die set_value() Methode eines std::promise an den Thread, der die get() eines std::future um die Daten zu empfangen. Eine Ausnahme wird erzeugt, wenn die get() Methode eines Futures mehr als einmal aufgerufen wird.

Wenn der Thread mit dem std::promise hat nicht verwendet set_value() um sein Versprechen zu erfüllen, wenn der zweite Thread die get() der std::future um das Versprechen einzuholen, geht der zweite Thread in einen Wartezustand über, bis das Versprechen vom ersten Thread mit der std::promise wenn es die set_value() Methode, um die Daten zu senden.

Mit den vorgeschlagenen Koroutinen von Technische Spezifikation N4663 Programmiersprachen - C++-Erweiterungen für Coroutines und die Visual Studio 2017 C++ Compiler Unterstützung von co_await ist es auch möglich, Folgendes zu verwenden std::future et std::async um Coroutine-Funktionalität zu schreiben. Siehe die Diskussion und das Beispiel in https://stackoverflow.com/a/50753040/1466970 die in einem Abschnitt die Verwendung von std::future mit co_await .

Der folgende Beispielcode, eine einfache Visual Studio 2013-Windows-Konsolenanwendung, zeigt die Verwendung einiger der C++11-Gleichzeitigkeitsklassen/-templates und anderer Funktionen. Es veranschaulicht eine Verwendung für Versprechen/Zukunft, die gut funktioniert, autonome Threads, die eine Aufgabe ausführen und dann anhalten, und eine Verwendung, bei der ein synchroneres Verhalten erforderlich ist und das Versprechen/Zukunft-Paar aufgrund des Bedarfs an mehreren Benachrichtigungen nicht funktioniert.

Ein Hinweis zu diesem Beispiel sind die an verschiedenen Stellen eingefügten Verzögerungen. Diese Verzögerungen wurden nur hinzugefügt, um sicherzustellen, dass die verschiedenen Meldungen auf der Konsole mit std::cout dass der Text aus den verschiedenen Threads klar ist und sich nicht vermischt.

Der erste Teil der main() erstellt drei zusätzliche Threads und verwendet std::promise et std::future um Daten zwischen den Threads zu senden. Ein interessanter Punkt ist, wenn der Hauptthread einen Thread, T2, startet, der auf Daten vom Hauptthread wartet, etwas tut und dann Daten an den dritten Thread, T3, sendet, der dann etwas tut und Daten zurück an den Hauptthread sendet.

Der zweite Teil der main() erstellt zwei Threads und eine Reihe von Warteschlangen, um mehrere Nachrichten vom Hauptthread an jeden der beiden erstellten Threads zu ermöglichen. Wir können nicht verwenden std::promise et std::future denn das Versprechen/Zukunftsduo ist einmalig und kann nicht wiederholt werden.

Die Quelle für die Klasse Sync_queue stammt aus Stroustrups The C++ Programming Language: 4. Auflage.

// 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;
}

Diese einfache Anwendung erzeugt die folgende Ausgabe.

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X