阅读导航
- 引言
- 一、异步的概念
- 二、应用场景
- 1. 异步任务处理
- 2. 并发控制
- 3. 结果获取
- 三、使用示例
- 1. 使用std::async关联异步任务
- 💻示例代码
- 说明
- 2. 使用std::packaged_task和std::future配合
- (1)定义std::packaged_task
- (2)获取std::future对象
- (3)启动异步任务
- (4)等待异步任务完成并获取结果
- 3. 使用std::promise和std::future配合
- (1)创建std::promise对象
- (2)获取std::future对象
- (3)传递std::future对象
- (4)在产生结果的线程中设置结果
- (5)在消费结果的线程中获取结果
- 📦示例代码
引言
C++11的推出,为C++编程语言带来了革命性的变化,其中std::future
类作为异步编程的核心工具,让并发和异步任务的管理变得更加简洁和高效。本文将简要介绍std::future
类的基本概念和用法,并通过示例展示其在实际编程中的应用,帮助您更好地理解和利用这一C++11的新特性。
一、异步的概念
异步编程是一种编程范式,它允许程序在等待某个长时间运行的操作(如文件读写、网络通信或复杂计算)完成时,不会阻塞或挂起执行线程,而是可以继续执行其他任务。这种非阻塞的执行方式可以显著提高程序的响应性和吞吐量。
在C++中,异步编程的概念通过C++11标准引入的一系列新特性得到了极大的支持和简化,其中std::future
类扮演了关键角色。std::future
是一个模板类,用于表示异步操作的结果。它提供了一种机制,允许程序在将来的某个时刻访问该结果,而无需在异步操作完成之前阻塞执行线程。
🔴官方文档
二、应用场景
1. 异步任务处理
在处理需要较长时间完成的任务,如网络请求、大规模数据处理或复杂计算时,std::future
提供了一种机制来代表这些异步任务的结果。通过将这些耗时的操作从主线程中分离出来,在后台执行,我们可以让主线程继续处理其他任务,从而实现任务的并行处理。这不仅提高了程序的响应速度,还优化了整体执行效率。
2. 并发控制
在多线程编程环境中,经常需要确保某些操作在另一些操作完成之后才能执行,以维护程序的状态一致性和正确性。std::future
允许我们在多线程之间实现同步控制。通过等待std::future
对象代表的异步任务完成,我们可以确保在继续执行依赖于该任务结果的操作之前,该任务已经被成功完成。这种机制有助于简化并发控制逻辑,减少错误和竞态条件的发生。
3. 结果获取
std::future
提供了一种安全且便捷的方式来获取异步任务的结果。通过调用std::future::get()
成员函数,我们可以尝试检索异步操作的结果。然而,需要注意的是,如果异步操作尚未完成,调用get()
函数将会阻塞当前线程,直到异步操作完成并返回结果。这种方式确保了我们在继续处理结果之前,确实已经获得了所需的数据,从而避免了潜在的数据竞争和错误。因此,std::future
提供了一种可靠的机制来同步访问异步操作的结果。
三、使用示例
1. 使用std::async关联异步任务
在C++中,std::async
是<future>
库中的一个功能强大的工具,它允许你以异步方式启动一个任务,并且这个任务可以立即返回一个std::future
对象,通过这个对象你可以在未来某个时刻获取到任务的结果。使用std::async
可以很方便地实现并行计算或提高程序的响应性。
💻示例代码
假设我们有两个函数,分别用于执行一些耗时的计算:
#include <iostream>
#include <future>
#include <chrono>
#include <thread>
// 第一个耗时任务
int task1() {
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
return 42; // 假设的返回值
}
// 第二个耗时任务
int task2() {
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时操作
return 24; // 假设的返回值
}
int main() {
// 启动两个异步任务
auto future1 = std::async(std::launch::async, task1);
auto future2 = std::async(std::launch::async, task2);
// 等待并获取两个异步任务的结果
int result1 = future1.get();
int result2 = future2.get();
// 输出结果
std::cout << "Task 1 result: " << result1 << std::endl;
std::cout << "Task 2 result: " << result2 << std::endl;
return 0;
}
说明
-
启动异步任务:使用
std::async
时,你需要指定任务的启动策略(std::launch::async
、std::launch::deferred
或它们的组合)和要异步执行的函数。在这个例子中,我们使用了std::launch::async
来确保任务在新的线程中立即开始执行。 -
获取任务结果:
std::async
返回一个std::future
对象,这个对象代表了异步操作的结果。你可以通过调用future.get()
来等待异步操作完成并获取其结果。注意,get()
会阻塞调用它的线程,直到异步操作完成。 -
并发执行:在这个例子中,
task1
和task2
会并发执行,因为我们在主线程中几乎同时启动了它们。它们的执行顺序和完成时间取决于操作系统的调度。
在C++中,std::packaged_task
与std::future
是紧密相关的,它们通常结合使用以实现异步编程和结果传递。std::packaged_task
是一个可调用的对象,它封装了一个可以异步执行的函数、lambda表达式、绑定表达式或其他可调用对象,并将该函数的执行结果存储在与std::future
相关联的共享状态中。
2. 使用std::packaged_task和std::future配合
(1)定义std::packaged_task
首先,需要定义一个std::packaged_task
对象,并为其提供一个返回特定类型结果的函数或可调用对象。这个函数的返回类型将与std::future
的类型相关联。
#include <future>
#include <iostream>
int compute_value(int x) {
// 假设这是一个耗时的计算
return x * x;
}
int main() {
std::packaged_task<int(int)> task(compute_value);
// ...
}
(2)获取std::future对象
通过调用std::packaged_task
的get_future()
成员函数来获取一个std::future
对象。这个future
对象将用于稍后检索异步操作的结果。
std::future<int> result = task.get_future();
(3)启动异步任务
std::packaged_task
对象可以作为函数对象被调用,但通常不会直接在原线程中这样做,而是将它绑定到一个线程(例如,使用std::thread
)或某个异步执行机制(如线程池)上,以异步方式执行。
std::thread worker(std::move(task), 42); // 传递任务和一个参数
// ...
}
注意:在将std::packaged_task
传递给线程之前,必须先获取std::future
对象,因为一旦std::packaged_task
被移动到另一个线程,你就不能再访问原始对象来获取std::future
了。
(4)等待异步任务完成并获取结果
在主线程中,你可以通过调用std::future
的get()
方法来等待异步任务完成并获取结果。调用get()
会阻塞当前线程,直到结果可用。
worker.join(); // 等待线程完成
std::cout << "The result is " << result.get() << std::endl;
🚨🚨注意:std::future::get()
只能被调用一次,因为结果一旦被取出就无法再次访问。
3. 使用std::promise和std::future配合
在C++中,std::promise
和std::future
是紧密相关的,它们用于在不同线程之间传递值或异常。std::promise
对象允许你在一个线程中设置结果值或异常,而std::future
对象则用于在另一个线程中获取这些值或异常。这种机制特别适用于异步编程,其中任务的执行和结果的使用可能发生在不同的线程中。
(1)创建std::promise对象
首先,在产生结果的线程中创建一个std::promise
对象。这个对象将用于设置结果值或异常。
(2)获取std::future对象
通过调用std::promise
对象的get_future()
成员函数来获取一个std::future
对象。这个future
对象将用于在另一个线程中获取结果。
(3)传递std::future对象
将std::future
对象传递给需要结果的线程。这通常通过函数参数、全局变量、共享数据结构或其他线程间通信机制来完成。
(4)在产生结果的线程中设置结果
在产生结果的线程中,使用std::promise
对象的set_value()
成员函数来设置结果值,或者使用set_exception()
来设置异常(如果需要的话)。一旦设置了值或异常,与之关联的future
对象就会变为“就绪”状态。
(5)在消费结果的线程中获取结果
在消费结果的线程中,使用std::future
对象的get()
成员函数来获取结果。如果结果已经就绪,get()
将立即返回结果值。如果结果尚未就绪,get()
将阻塞当前线程,直到结果变为就绪状态。
📦示例代码
#include <iostream>
#include <future>
#include <thread>
void compute_and_set_result(std::promise<int> prom) {
// 假设这是一个耗时的计算
int result = 42; // 假设的计算结果
// 设置结果值
prom.set_value(result);
}
int main() {
// 创建一个promise对象
std::promise<int> prom;
// 获取与promise关联的future对象
std::future<int> fut = prom.get_future();
// 启动一个线程来执行耗时的计算并设置结果
std::thread worker(compute_and_set_result, std::move(prom));
// 等待结果并打印
std::cout << "The result is " << fut.get() << std::endl;
// 确保线程完成
worker.join();
return 0;
}