The class template
std::promiseprovides a facility to store a value or an exception that is later acquired asynchronously via astd::futureobject created by thestd::promiseobject. Note that thestd::promiseobject is meant to be used only once. -- cppreference/promise
# 1、概述
std::promise 是 C++11 引入的一种线程间通信机制,用于在一个线程中设置值,然后在另一个线程中获取该值。它通常与 std::future 一起使用, std::promise 设置值,而 std::future 获取值。
其主要用途包括:
在异步操作中传递结果: 一个线程(生产者)计算一个结果,并将结果设置到
std::promise中,另一个线程(消费者)通过与std::promise关联的std::future获取结果。实现任务编排: 可以将
std::promise作为任务的状态指示器,一个任务通过设置std::promise的值来通知另一个任务它已经完成。异常处理: 可以在一个线程中设置
std::promise异常,然后在另一个线程中通过与std::future关联的std::future::get()来捕获异常。
以下是一个简单示例,在函数 addTwoValue 中,我们传入一个 promise 并用它来设置计算结果,在 main 函数中,我们创建一个线程函数用于执行 addTwoValue ,然后获取与 promise 相绑定的 future,并通过该 future 能够获取到 promise 设置的计算结果。(如果计算未完成,promise 还未设置结果,则 future 会阻塞在 get 函数)
1 |
|
这里稍微与下面这个示例做辨析:
1 |
|
对于 promise 的使用,我们通常用其来设置值,用 future 来获取值,而这个 future,需要是 promise 通过
get_future 接口返回的与该 promise 相绑定的 future。至于 future 的获取时机,可以在定义 promise 之后就立即获取 future,如上面的示例代码 1,也可以在即将使用时再获取 future,如上面的示例代码 2。
而当我们希望在使用时再获取 future 时,需要注意,对于示例代码 1,由于在 task 传递线程参数的时候使用了移动语义,所以 task 之后的 prom 已经不再有效。所以在示例代码 2 中,我们向 addTwoValue 传递的 promise 是引用,在 task 中我们也需要用 std::ref 进行引用传递。
# 2、promise 在 thread 的传递
为什么在 thread 构造函数传递 promise 时,需要用到
std::move或者std::ref呢?
在 C++ 中,传递 promise 对象给 std::thread 的构造函数时,使用 std::move 或 std::ref 是为了确保 promise 对象被正确地传递到线程函数中。
# 2.1 使用 std::move
std::move 用于将一个对象从一个作用域转移到另一个作用域,而不进行复制。
这对 promise 对象特别重要,因为 promise 不能被复制,只能被移动。
示例:
1 |
|
在这个例子中,
std::move 将 p 转移到 set_value 函数中,而不是复制它。如果不使用
std::move ,编译器会报错,因为 std::promise 对象是不可复制的。# 2.1 使用 std::ref
std::ref 用于创建一个对象的引用包装器,这样对象可以以引用的方式传递,而不是值的方式。
这在某些情况下非常有用,特别是当你不想移动对象,而是希望在多个地方使用同一个对象。
示例:
1 |
|
在这个例子中,
std::ref(p) 创建了一个对 p 的引用包装器,传递给线程函数。这样,线程函数中的
set_value 使用的是主线程中的 promise 对象的引用,而不是一个拷贝或移动的对象。# 2.3 为什么需要 std::move 或 std::ref
- 不可复制性:
std::promise对象是不可复制的,因此不能通过值传递给std::thread的构造函数。必须通过移动语义std::move或引用std::ref来传递。 - 避免资源浪费:移动语义允许资源从一个对象转移到另一个对象,而不是复制所有资源,从而提高了效率。
- 线程安全:确保在多线程环境中,
promise对象的状态和结果的设置是安全的。
# 2.4 总结
- 使用
std::move将promise对象的所有权转移到线程函数中,这样可以避免复制问题。 - 使用
std::ref传递promise对象的引用,使得线程函数操作的是原始对象,而不是它的副本。
这两种方法都确保了 promise 对象能够正确地传递给线程函数,并避免了编译错误和资源管理问题。
# 3、promise 的一次性
如何理解 “Note that the
std::promiseobject is meant to be used only once. ” 这句话?
理解 “Note that the std::promise object is meant to be used only once.” 这句话的关键在于理解 std::promise 对象的设计意图和使用场景。 std::promise 对象的核心作用是与 std::future 对象配合使用,在多线程编程中提供一种从一个线程向另一个线程传递结果的机制。
# 3.1 std::promise 的一次性使用性
std::promise 对象的生命周期和与其关联的 std::future 对象密切相关。一个 std::promise 对象只能设置一次值,原因如下:
状态转换:
std::promise对象内部有一个状态机,用于跟踪其状态(未设置值、已设置值、异常状态等)。一旦promise对象设置了值或异常,它的状态就不可再变更。数据传递的唯一性:在并发编程中,
std::promise和std::future组合使用的主要目的是将一个线程生成的结果传递给另一个线程。如果允许std::promise多次设置值,会导致多次传递,破坏了这种一对一的传递模型。同步机制:
std::future对象通过等待std::promise对象设置值来同步线程。如果std::promise可以多次设置值,std::future就无法明确何时获取最终结果,从而导致同步机制失效。
# 3.2 总结
“ std::promise 对象只能使用一次” 这句话意味着:
- 一个
std::promise对象只能设置一次值或一次异常。 - 一旦设置,
std::promise对象的状态就固定了,不能再更改。 - 这种设计保证了
std::future对象能明确知道何时可以获取结果,从而实现线程之间的可靠同步。
理解这一点对于正确使用 std::promise 和 std::future 进行线程间通信非常重要。如果需要传递多个值,可以使用其他同步机制或创建多个 std::promise 和 std::future 对象来实现。
参考链接:
stackoverflow: what is promise?