我相当熟悉c++ 11的std::thread, std::async和std::future组件(例如,请看这个答案),这些都很简单。

然而,我不能完全理解std::promise是什么,它做什么以及在什么情况下最好使用它。标准文档本身除了类概要之外不包含大量信息,std::thread也不包含。

谁能给一个简要的例子,说明需要std::promise的情况,以及它是最常用的解决方案的情况?


当前回答

std::promise被创建为promise/future对的端点,std::future(使用get_future()方法从std::promise创建)是另一个端点。这是一种简单的一次性方法,当一个线程通过消息向另一个线程提供数据时,它为两个线程提供了一种同步方式。

您可以将其视为一个线程创建一个承诺来提供数据,而另一个线程在未来收集承诺。此机制只能使用一次。

promise/future机制只是一个方向,从使用std::promise的set_value()方法的线程到使用std::future的get()方法来接收数据的线程。如果调用future的get()方法超过一次,则会生成异常。

如果带有std::promise的线程没有使用set_value()来履行其承诺,那么当第二个线程调用std::future的get()来收集承诺时,第二个线程将进入等待状态,直到带有std::promise的第一个线程在使用set_value()方法发送数据时完成承诺。

随着技术规范N4663编程语言- c++扩展协程的提议和Visual Studio 2017 c++编译器对co_await的支持,也可以使用std::future和std::async来编写协程功能。请参阅https://stackoverflow.com/a/50753040/1466970中的讨论和示例,其中有一节讨论了std::future与co_await的使用。

下面的示例代码是一个简单的Visual Studio 2013 Windows控制台应用程序,展示了使用一些c++ 11并发类/模板和其他功能。它说明了promise/future的使用情况,自治线程将完成一些任务并停止,以及需要更多同步行为的使用情况,由于需要多个通知,promise/future对无法工作。

关于这个例子需要注意的一点是在不同的地方添加了延迟。添加这些延迟只是为了确保使用std::cout打印到控制台的各种消息是清晰的,并且来自几个线程的文本不会混合在一起。

main()的第一部分是创建另外三个线程,并使用std::promise和std::future在线程之间发送数据。一个有趣的点是主线程启动一个线程T2,它将等待主线程的数据,执行一些操作,然后将数据发送给第三个线程T3, T3将执行一些操作并将数据发送回主线程。

main()的第二部分创建两个线程和一组队列,以允许多条消息从主线程发送到创建的两个线程。我们不能为此使用std::promise和std::future,因为promise/future是一次性的,不能重复使用。

类Sync_queue的源代码来自Stroustrup的《c++ Programming Language: 4th Edition》。

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

其他回答

在一个粗略的近似中,你可以把std::promise看作std::future的另一端(这是错误的,但是为了说明问题,你可以把它看作是)。通信通道的消费者端将使用std::future来消费来自共享状态的数据,而生产者线程将使用std::promise来写入共享状态。

承诺是电线的另一端。

假设您需要检索由异步程序计算的future的值。然而,你不希望它在同一个线程中计算,你甚至不“现在”生成一个线程——也许你的软件被设计为从线程池中选择一个线程,所以你不知道谁将在最后执行这些计算。

现在,你传递什么给这个(未知的)线程/类/实体?你不通过将来,因为这是结果。你想要传递一些连接到未来的东西,它代表导线的另一端,所以你只会查询未来,而不知道谁会实际计算/写入一些东西。

这就是承诺。它是连接你未来的手柄。如果future是一个演讲者,并且使用get()开始听直到一些声音出来,那么promise就是一个麦克风;但不是普通的麦克风,它是通过一根电线连接到你手中的扬声器的麦克风。你可能知道电话的另一端是谁,但你不需要知道——你只需要把电话给对方,然后等对方开口。

在异步处理中有3个核心实体。c++ 11目前专注于其中的两个。

异步运行逻辑所需要的核心内容是:

任务(逻辑打包为某个函子对象)将运行“某处”。 实际的处理节点——线程、进程等,当这些函子被提供给它时运行它们。查看“Command”设计模式,了解基本工作线程池是如何做到这一点的。 结果句柄:有人需要这个结果,需要一个对象来为他们获取结果。出于OOP和其他原因,任何等待或同步都应该在这个句柄的api中完成。

c++ 11调用了我在(1)std::promise和(3)std::future中提到的东西。 std::thread是(2)唯一公开提供的东西。这是不幸的,因为真正的程序需要管理线程和内存资源,大多数程序都希望任务运行在线程池上,而不是为每个小任务创建和销毁一个线程(这几乎总是会导致不必要的性能损失,并且很容易造成更糟糕的资源短缺)。

根据Herb Sutter和c++ 11智囊团的其他人的说法,他们暂定计划添加std::executor,这很像Java,将是线程池的基础,逻辑上类似于(2)的设置。也许我们会在c++ 2014中看到它,但我打赌更像c++ 17(如果他们搞砸了这些标准,上帝会帮助我们)。

用[期货]的话说。一个std::future是一个异步返回对象(“一个从共享状态读取结果的对象”),一个std::promise是一个异步提供者(“一个为共享状态提供结果的对象”),即一个promise是你设置结果的对象,这样你就可以从关联的future中获得它。

异步提供程序最初创建future引用的共享状态。Std::promise是异步提供程序的一种类型,Std::packaged_task是另一种类型,Std::async的内部细节是另一种类型。它们中的每一个都可以创建一个共享状态,并为您提供一个共享该状态的std::future,并可以使该状态就绪。

async是一个更高级的便利实用程序,它为您提供一个异步结果对象,并在内部负责创建异步提供程序,并在任务完成时准备好共享状态。你可以用std::packaged_task(或std::bind和std::promise)和std::thread来模拟它,但使用std::async更安全、更容易。

std::promise is a bit lower-level, for when you want to pass an asynchronous result to the future, but the code that makes the result ready cannot be wrapped up in a single function suitable for passing to std::async. For example, you might have an array of several promises and associated futures and have a single thread which does several calculations and sets a result on each promise. async would only allow you to return a single result, to return several you would need to call async several times, which might waste resources.

Bartosz milwski提供了一个很好的记录。

c++将期货的实现划分为一个集合 小块的

性病:承诺是这些部分之一。

承诺是传递返回值(或对象)的载体 异常)从执行函数的线程到线程 这就利用了未来的功能。

...

对象周围构造的同步对象 承诺信道的接收端。

所以,如果你想使用一个未来,你最终会得到一个承诺,你用它来获得异步处理的结果。

该页面的一个例子是:

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