The class template
std::promise
provides a facility to store a value or an exception that is later acquired asynchronously via astd::future
object created by thestd::promise
object. Note that thestd::promise
object 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::promise
object 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?