快速了解std::promise的工作原理和使用

第1章: 基本介绍

理解 std::promise 的关键在于明白它是如何与 std::future 配合工作的,以及它在异步编程中扮演的角色。让我们一步步来解释这个过程。

工作原理

  1. 创建 std::promisestd::future 对象

    • 当你创建一个 std::promise 对象时,它会自动创建一个与之关联的 std::future 对象。这个 future 对象用于在稍后某个时间点获取 promise 提供的值。
  2. 在生产者端设置值

    • std::promise 对象通常在某个异步操作(生产者)中被使用。当这个异步操作完成后,你可以通过 std::promiseset_value() 方法来设置一个值(或通过 set_exception() 设置一个异常)。
    • 一旦 set_value() 被调用,与之关联的 std::future 就会被通知,表示值已经就绪。
  3. 在消费者端获取值

    • std::future 对象通常在另一个线程(消费者)中被使用。消费者线程可以调用 std::futureget() 方法来获取由 std::promise 设置的值。如果值尚未设置,get() 方法将阻塞当前线程,直到值可用。
    • 如果 std::promise 设置了异常,那么在调用 get() 时这个异常会被抛出。

应用场景举例

假设你有一个计算密集型的任务,比如计算大数的因子,这个任务在一个单独的线程中异步执行。你可以使用 std::promise 来传递计算结果回主线程。

cpp 复制代码
#include <future>
#include <thread>
#include <iostream>

void compute(std::promise<int>&& p) {
    // 假设这里有一个复杂的计算
    int result = 42; // 计算结果
    p.set_value(result); // 将结果传递给promise
}

int main() {
    std::promise<int> p;
    std::future<int> f = p.get_future(); // 获取与promise关联的future
    std::thread t(compute, std::move(p)); // 启动一个线程来执行计算

    // 在主线程中等待结果
    int result = f.get(); // 这里会阻塞,直到compute函数设置了promise的值
    std::cout << "Result is: " << result << std::endl;

    t.join(); // 等待线程结束
    return 0;
}

在这个例子中,std::promise 被用来在计算线程中设置一个值,而 std::future 被用来在主线程中获取这个值。这就是 std::promise 作为向异步操作提供结果的接口的典型用法。

第2章: 使用核心 std::promise 的主要用途是与 set_value 方法配合使用来设置对应的值。这是 std::promise 设计的核心功能。让我们详细了解一下这个过程:

  1. 设置值(set_value:

    • 使用 std::promiseset_value 方法来设置一个值是其最常见的用途。当你在某个线程中执行某个操作,并且希望将结果传递给另一个线程时,你可以使用 std::promise
    • 一旦 set_value 被调用,它就会将值传递给与之关联的 std::future 对象。这意味着,任何正在等待该 futureget 方法的线程将会收到这个值并继续执行。
  2. 异常处理(set_exception:

    • 除了 set_valuestd::promise 还提供了 set_exception 方法。这允许你传递一个异常而不是一个正常值。如果在执行异步操作期间发生错误,这会非常有用。
    • std::futureget 方法被调用时,如果 std::promise 设置了异常,那么这个异常将被重新抛出。
  3. 自动状态转换:

    • 如果你没有显式地调用 set_valueset_exception,并且 std::promise 的对象被销毁(例如,离开了其作用域),那么与之关联的 std::future 将接收到一个特殊的异常(std::future_error),表示该 promise 没有正确地设置值。
  4. std::async 的关系:

    • 当你使用 std::async 启动一个异步任务时,它内部实际上是创建了一个 std::promise,并在异步操作完成时设置值。这是为什么你可以从 std::async 返回的 std::future 获取结果的原因。

因此,std::promise 的主要功能是在异步编程中作为值或异常的设置点,而与之关联的 std::future 则用于在其他线程中获取这些值或异常。这种机制使得线程间的数据传递和异常处理变得更加安全和方便。 第3章:其他介绍 在 std::promise 中使用 set_value 方法设置值时,既可以使用左值(l-values)也可以使用右值(r-values),但是具体的行为取决于你是如何使用它的。这里涉及到 C++ 的左值和右值的概念,以及移动语义和拷贝语义:

  1. 左值(L-values):

    • 如果你使用左值(已命名的对象或可寻址的表达式)调用 set_value,那么该值会被拷贝到 std::promise 关联的存储中。这就意味着,即使原始左值在 set_value 调用之后被修改或销毁,std::future 获取的值也不会受到影响。
  2. 右值(R-values):

    • 如果你使用右值(临时对象或可以被移动的对象)调用 set_value,则该值会被移动到 std::promise 的存储中(如果移动构造函数可用)。这通常更有效,因为它避免了不必要的拷贝,尤其是对于大型对象或资源密集型对象而言。
  3. 移动语义(Move Semantics):

    • 在 C++11 及更高版本中,移动语义允许资源(如动态内存、文件句柄等)从一个对象转移到另一个对象,这样可以避免复制大量数据,提高效率。如果对象支持移动语义,使用右值作为 set_value 的参数是更高效的选择。
  4. 拷贝语义(Copy Semantics):

    • 如果对象不支持移动语义,或者你显式地使用了左值,那么 set_value 将执行拷贝操作。这意味着在 promisefuture 之间传递的数据是原始数据的副本。

综上所述,你可以使用左值或右值作为 set_value 的参数,但是最佳实践是当可能时使用右值(尤其是对于大型或复杂对象),以利用移动语义的效率优势。然而,这也取决于你的具体情况和对象类型。对于简单或小型对象,拷贝和移动之间的性能差异可能微乎其微。


std::promise 是一个模板类,它设计得足够通用,可以与几乎任何类型兼容。这包括 POD(Plain Old Data,普通旧数据)类型、基本数据类型(如 intdouble 等),以及更复杂的数据结构(如自定义类、STL 容器等)。这种通用性是通过模板编程实现的,它允许 std::promise 与多种不同类型的值一起工作。

兼容的类型

  1. 基本数据类型

    • std::promise 可以用于诸如 intfloatchar 等基本数据类型。这些类型通常易于复制,并且不涉及特殊的内存管理问题。
  2. POD类型

    • POD类型,即简单的结构体或联合体,不含有构造函数、析构函数、虚函数等,也可以通过 std::promise 传递。由于它们通常也是简单的数据结构,因此通常也很适合用于异步操作。
  3. 复杂数据结构

    • 对于类实例、STL 容器(如 std::vectorstd::map 等)以及其他更复杂的数据结构,std::promise 同样适用。但在这种情况下,需要特别注意对象的拷贝和移动语义。确保这些复杂类型具有有效的拷贝构造函数和/或移动构造函数是很重要的,尤其是当这些类型包含对动态分配资源的管理时。
  4. 自定义类型

    • 对于用户定义的类型,std::promise 能够很好地工作,只要这些类型遵守了C++的拷贝和移动语义规则。这意味着你的类需要有适当的拷贝构造函数、移动构造函数、拷贝赋值运算符和移动赋值运算符。

注意事项

  • 异常安全 :在使用 std::promise 传递复杂类型时,应当确保你的代码对异常是安全的。如果在复制或移动操作期间抛出异常,你需要确保它被正确地处理。
  • 性能考虑 :对于大型或复杂的对象,使用 std::promise 时要特别注意性能问题。在这些情况下,利用移动语义(如果可行)来减少不必要的数据复制是非常重要的。

总之,std::promise 提供了一种灵活的方式来在不同线程之间传递几乎任何类型的数据,但在使用它时,了解和遵守相关的C++编程规范是非常重要的。

相关推荐
y先森2 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy2 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189112 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿3 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡4 小时前
commitlint校验git提交信息
前端
虾球xz4 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇5 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒5 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员5 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐5 小时前
前端图像处理(一)
前端