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

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

第1章: 基本介绍

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

工作原理

  1. 创建 std::promisestd::future 对象
  • 当你创建一个 std::promise 对象时,它会自动创建一个与之关联的 std::future 对象。这个 future 对象用于在稍后某个时间点获取 promise 提供的值。
  1. 在生产者端设置值
  • std::promise 对象通常在某个异步操作(生产者)中被使用。当这个异步操作完成后,你可以通过 std::promiseset_value() 方法来设置一个值(或通过 set_exception() 设置一个异常)。
  • 一旦 set_value() 被调用,与之关联的 std::future 就会被通知,表示值已经就绪。
  1. 在消费者端获取值
  • std::future 对象通常在另一个线程(消费者)中被使用。消费者线程可以调用 std::futureget() 方法来获取由 std::promise 设置的值。如果值尚未设置,get() 方法将阻塞当前线程,直到值可用。
  • 如果 std::promise 设置了异常,那么在调用 get() 时这个异常会被抛出。

应用场景举例

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

#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 方法的线程将会收到这个值并继续执行。
  1. 异常处理(set_exception:
  • 除了 set_valuestd::promise 还提供了 set_exception 方法。这允许你传递一个异常而不是一个正常值。如果在执行异步操作期间发生错误,这会非常有用。
  • std::futureget 方法被调用时,如果 std::promise 设置了异常,那么这个异常将被重新抛出。
  1. 自动状态转换:
  • 如果你没有显式地调用 set_valueset_exception,并且 std::promise 的对象被销毁(例如,离开了其作用域),那么与之关联的 std::future 将接收到一个特殊的异常(std::future_error),表示该 promise 没有正确地设置值。
  1. 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 获取的值也不会受到影响。
  1. 右值(R-values):
  • 如果你使用右值(临时对象或可以被移动的对象)调用 set_value,则该值会被移动到 std::promise 的存储中(如果移动构造函数可用)。这通常更有效,因为它避免了不必要的拷贝,尤其是对于大型对象或资源密集型对象而言。
  1. 移动语义(Move Semantics):
  • 在 C++11 及更高版本中,移动语义允许资源(如动态内存、文件句柄等)从一个对象转移到另一个对象,这样可以避免复制大量数据,提高效率。如果对象支持移动语义,使用右值作为 set_value 的参数是更高效的选择。
  1. 拷贝语义(Copy Semantics):
  • 如果对象不支持移动语义,或者你显式地使用了左值,那么 set_value 将执行拷贝操作。这意味着在 promisefuture 之间传递的数据是原始数据的副本。

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


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

兼容的类型

  1. 基本数据类型
  • std::promise 可以用于诸如 intfloatchar 等基本数据类型。这些类型通常易于复制,并且不涉及特殊的内存管理问题。
  1. POD类型
  • POD类型,即简单的结构体或联合体,不含有构造函数、析构函数、虚函数等,也可以通过 std::promise 传递。由于它们通常也是简单的数据结构,因此通常也很适合用于异步操作。
  1. 复杂数据结构
  • 对于类实例、STL 容器(如 std::vectorstd::map 等)以及其他更复杂的数据结构,std::promise 同样适用。但在这种情况下,需要特别注意对象的拷贝和移动语义。确保这些复杂类型具有有效的拷贝构造函数和/或移动构造函数是很重要的,尤其是当这些类型包含对动态分配资源的管理时。
  1. 自定义类型
  • 对于用户定义的类型,std::promise 能够很好地工作,只要这些类型遵守了C++的拷贝和移动语义规则。这意味着你的类需要有适当的拷贝构造函数、移动构造函数、拷贝赋值运算符和移动赋值运算符。

注意事项

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

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

工作原理

  1. 创建 std::promisestd::future 对象
  • 当你创建一个 std::promise 对象时,它会自动创建一个与之关联的 std::future 对象。这个 future 对象用于在稍后某个时间点获取 promise 提供的值。
  1. 在生产者端设置值
  • std::promise 对象通常在某个异步操作(生产者)中被使用。当这个异步操作完成后,你可以通过 std::promiseset_value() 方法来设置一个值(或通过 set_exception() 设置一个异常)。
  • 一旦 set_value() 被调用,与之关联的 std::future 就会被通知,表示值已经就绪。
  1. 在消费者端获取值
  • std::future 对象通常在另一个线程(消费者)中被使用。消费者线程可以调用 std::futureget() 方法来获取由 std::promise 设置的值。如果值尚未设置,get() 方法将阻塞当前线程,直到值可用。
  • 如果 std::promise 设置了异常,那么在调用 get() 时这个异常会被抛出。

应用场景举例

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

#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 方法的线程将会收到这个值并继续执行。
  1. 异常处理(set_exception:
  • 除了 set_valuestd::promise 还提供了 set_exception 方法。这允许你传递一个异常而不是一个正常值。如果在执行异步操作期间发生错误,这会非常有用。
  • std::futureget 方法被调用时,如果 std::promise 设置了异常,那么这个异常将被重新抛出。
  1. 自动状态转换:
  • 如果你没有显式地调用 set_valueset_exception,并且 std::promise 的对象被销毁(例如,离开了其作用域),那么与之关联的 std::future 将接收到一个特殊的异常(std::future_error),表示该 promise 没有正确地设置值。
  1. 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 获取的值也不会受到影响。
  1. 右值(R-values):
  • 如果你使用右值(临时对象或可以被移动的对象)调用 set_value,则该值会被移动到 std::promise 的存储中(如果移动构造函数可用)。这通常更有效,因为它避免了不必要的拷贝,尤其是对于大型对象或资源密集型对象而言。
  1. 移动语义(Move Semantics):
  • 在 C++11 及更高版本中,移动语义允许资源(如动态内存、文件句柄等)从一个对象转移到另一个对象,这样可以避免复制大量数据,提高效率。如果对象支持移动语义,使用右值作为 set_value 的参数是更高效的选择。
  1. 拷贝语义(Copy Semantics):
  • 如果对象不支持移动语义,或者你显式地使用了左值,那么 set_value 将执行拷贝操作。这意味着在 promisefuture 之间传递的数据是原始数据的副本。

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


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

兼容的类型

  1. 基本数据类型
  • std::promise 可以用于诸如 intfloatchar 等基本数据类型。这些类型通常易于复制,并且不涉及特殊的内存管理问题。
  1. POD类型
  • POD类型,即简单的结构体或联合体,不含有构造函数、析构函数、虚函数等,也可以通过 std::promise 传递。由于它们通常也是简单的数据结构,因此通常也很适合用于异步操作。
  1. 复杂数据结构
  • 对于类实例、STL 容器(如 std::vectorstd::map 等)以及其他更复杂的数据结构,std::promise 同样适用。但在这种情况下,需要特别注意对象的拷贝和移动语义。确保这些复杂类型具有有效的拷贝构造函数和/或移动构造函数是很重要的,尤其是当这些类型包含对动态分配资源的管理时。
  1. 自定义类型
  • 对于用户定义的类型,std::promise 能够很好地工作,只要这些类型遵守了C++的拷贝和移动语义规则。这意味着你的类需要有适当的拷贝构造函数、移动构造函数、拷贝赋值运算符和移动赋值运算符。

注意事项

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

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

结语

在我们的编程学习之旅中,理解是我们迈向更高层次的重要一步。然而,掌握新技能、新理念,始终需要时间和坚持。从心理学的角度看,学习往往伴随着不断的试错和调整,这就像是我们的大脑在逐渐优化其解决问题的“算法”。

这就是为什么当我们遇到错误,我们应该将其视为学习和进步的机会,而不仅仅是困扰。通过理解和解决这些问题,我们不仅可以修复当前的代码,更可以提升我们的编程能力,防止在未来的项目中犯相同的错误。

我鼓励大家积极参与进来,不断提升自己的编程技术。无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力。

目录
相关文章
|
3月前
认识 std::async
认识 std::async
26 0
|
1月前
|
存储 安全 编译器
【C++ 包装器类 std::function 和 函数适配器 std::bind】 C++11 全面的std::function和std::bind的入门使用教程
【C++ 包装器类 std::function 和 函数适配器 std::bind】 C++11 全面的std::function和std::bind的入门使用教程
33 0
|
1月前
|
存储 设计模式 前端开发
【C++并发编程】std::future、std::async、std::packaged_task与std::promise的深度探索(二)
【C++并发编程】std::future、std::async、std::packaged_task与std::promise的深度探索
29 0
|
1月前
|
并行计算 前端开发 安全
【C++并发编程】std::future、std::async、std::packaged_task与std::promise的深度探索(一)
【C++并发编程】std::future、std::async、std::packaged_task与std::promise的深度探索
62 0
|
1月前
|
存储 前端开发 安全
【C++并发编程】std::future、std::async、std::packaged_task与std::promise的深度探索(三)
【C++并发编程】std::future、std::async、std::packaged_task与std::promise的深度探索
24 0
|
3月前
|
前端开发 C++
C++11实用技术(三)std::future、std::promise、std::packaged_task、async
C++11实用技术(三)std::future、std::promise、std::packaged_task、async
26 0
|
3月前
C++11实用技术(二)std::function和bind绑定器
C++11实用技术(二)std::function和bind绑定器
26 0
|
3月前
|
前端开发
[C++11]std::promise介绍及使用
[C++11]std::promise介绍及使用
|
9月前
|
存储 前端开发 JavaScript
|
7月前
|
C++
C++11特性之std:call_once介绍
std:call_once是C++11引入的新特性,如需使用,只需要#include <mutex>即可,简单来说std:call_once的作用,确保函数或代码片段在多线程环境下,只需要执行一次,常用的场景如Init()操作或一些系统参数的获取等。
112 0