快速了解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++编程规范是非常重要的。

相关推荐
学习使我快乐0123 分钟前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
bobostudio199524 分钟前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式
黄尚圈圈1 小时前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水2 小时前
简洁之道 - React Hook Form
前端
正小安4 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch6 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光6 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   6 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   6 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web6 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery