C++异步调用 future async promise packaged_task
背景:
C++ 异步调用是现代 C++ 编程中的一种重要技术,它允许程序在等待某个任务完成时继续执行其他代码,从而提高程序的效率和响应性。
C++11 引入了 std::async、std::future 和 std::promise 等工具,使得异步编程变得更加方便和直观。以下是关于 C++ 异步调用的详细介绍,包括基本概念、使用方法和示例代码。
以下代码头文件为:
#include <iostream> #include <future> #include <thread> #include <chrono>
std::future:
std::future 是一个类模板,提供了一种访问异步操作结果的机制。
主要成员函数包括 get、wait 和 wait_for,其中 get 用于获取异步操作的结果,如果结果还不可用,get 会阻塞当前线程,直到结果可用。
注意:
一般要配合另外三个使用,否则发挥不出来效果。
get只能被调用一次,多次调用会抛异常。
调用wait()后,仍然可以使用get()来获取结果。
wait_for():等待异步操作完成,等待指定的时间段。如果超时了,会返回timeout.
std::async:
std::async 是一个函数模板,用于启动异步任务。它返回一个 std::future 对象,可以用来获取异步任务的结果。
std::async 可以指定执行策略。例如 std::launch::async 表示在新线程中异步执行,std::launch::deferred 表示延迟执行,直到调用 std::future::get 或 std::future::wait 时才开始执行。
代码:
int compute(int x) { std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作 return x * x; } int asyncTest() { // 启动异步任务 std::future<int> result = std::async(std::launch::async, compute, 5); // 继续执行其他代码 std::cout << "Doing some other work..." << std::endl; // 获取异步任务的结果 int value = result.get(); // 这里会等待。 std::cout << "Result: " << value << std::endl; return 0; }
std::promise:
std::promise 是一个类模板,用于在异步任务中设置结果值。std::promise 可以与 std::future 配合使用,通过 std::promise::set_value 设置结果值,然后通过 std::future::get 获取结果值。
如何理解:配合thread用的,用来获取另外一个线程的执行结果。
如图:1)启动线程B时,可以传一个菜篮子(就是promise)进去。2)当线程B干完活了,就可以往菜篮子放菜。3)线程A就能通过get获取菜篮子的菜了。
注意事项
1. std::promise的生命周期:确保std::promise对象在std:: future对象需要它之前保持有效。一旦std::promise对象被销毁,任何尝试通过std:: future对象访问其结果的操作都将失败。
2. 线程安全:std::promise的set_value和set_exception方法是线程安全的,但你应该避免在多个线程中同时调用它们,因为这通常意味着你的设计存在问题。
3. 异常处理:当使用std::promise时,要特别注意异常处理。如果std::promise的set_exception方法没有被调用,但异步操作中确实发生了异常,那么这些异常将不会被捕获,并可能导致程序崩溃。
4. 性能考虑:虽然std::promise和std::future提供了强大的异步编程能力,但它们也引入了额外的开销。在性能敏感的应用程序中,要仔细考虑是否真的需要它们。
5. std::move 的使用:在将std::promise对象传递给线程函数时,通常需要使用std::move来避免不必要的复制。这是因为std::promise对象通常包含非托管资源(如共享状态),复制它们可能是昂贵的或不必要的。
代码:
void doWork(std::promise<int>&& p) { std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作 p.set_value(42); // 设置结果值 } int promiseTest() { std::promise<int> prom; // 创建 promise std::future<int> fut = prom.get_future(); // 启动线程执行异步任务. 注意这里一定要用move进去,不能用拷贝构造。 std::thread t(doWork, std::move(prom)); // 继续执行其他代码 std::cout << "Doing some other work..." << std::endl; // 获取异步任务的结果 int value = fut.get(); std::cout << "Result: " << value << std::endl; t.join(); return 0; }
std::packaged_task:
std::packaged_task 是一个类模板,包装了一个可调用对象(如普通函数、lambda 表达式、函数对象等),以便异步调用。它也可以与 std::future 配合使用,通过 std::packaged_task::get_future 获取 std::future 对象。
就是把一堆函数封装了一下,方便异步调用,适合用来做线程池,把不同的任务封装成统一的packaged_task,然后统一调度:参考:https://www.cnblogs.com/xcywt/p/18429228
启动流程:
代码:
int computeTTT(int x) { std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作 return x * x; } int packaged_task_Test() { // 创建 packaged_task std::packaged_task<int(int)> task(computeTTT); // 把一个函数包装一下。 std::future<int> result = task.get_future(); // 启动线程执行异步任务 std::thread t(std::move(task), 5); // 继续执行其他代码 std::cout << "Doing some other work..." << std::endl; // 获取异步任务的结果 int value = result.get(); std::cout << "Result: " << value << std::endl; t.join(); return 0; }
比较:
async:使用简单;灵活性有限,无法控制任务的调度方式(比如在哪个线程启动)。适用于简单的异步任务,不用复杂的任务调度或管理。
promise:需要手动管理任务的运行和结果传递。一般配合thread使用,代码管理复杂一点。
packaged_task:适合需要高度封装的任务管理,特别适合自定义线程池或任务队列。