0、背景
在现代 C++ 中,std::async 和 std::packaged_task 是两个非常重要的工具,能够帮助我们更好地处理并发和异步操作。它们分别代表了异步执行任务的两种不同的方式,但都可以有效地将任务的执行从主线程或调用线程中分离出来,以提高程序的并发性和响应能力。
1、std::async
1.1、std::async的基本语法
std::async 是 C++11 引入的一个函数模板,它用于启动一个异步操作,并返回一个 std::future 对象,这个 future 对象可以用来获取异步操作的结果。std::async 会根据指定的策略异步地执行一个函数或可调用对象。函数签名如下:
cpp
std::future<T> std::async(std::launch policy, Func&& f, Args&&... args);
- policy: 启动策略,决定任务如何执行。它有两个可能的值:
std::launch::async :表示任务将异步执行(即在一个新的线程中执行);
std::launch::deferred:表示任务将延迟执行,直到调用 future.get() 或 future.wait() 来获取结果时才开始执行。 - f: 一个可调用对象(例如函数指针、lambda 表达式或函数对象)。
- args: 传递给可调用对象的参数
std::async 返回一个 std::future 类型的对象,T 是任务的返回类型。可以通过 future.get() 来获取任务的结果。需要注意的是,如果任务还没有完成,调用 get() 会阻塞直到任务完成。
1.2、使用用例
cpp
#include <iostream>
#include <future>
#include <thread>
int async_task(int x, int y) {
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时任务
return x + y;
}
int main() {
// 启动异步任务
std::future<int> result = std::async(std::launch::async, async_task, 5, 3);
// 在等待异步结果的同时,可以做其他工作
std::cout << "Doing other work in main thread..." << std::endl;
// 获取异步任务结果,get() 会阻塞直到任务完成
int sum = result.get();
std::cout << "Async task result: " << sum << std::endl;
return 0;
}
在上面的例子中,std::async 启动了一个异步任务,并返回一个 std::future 对象,主线程可以在不阻塞的情况下继续做其他工作,直到调用 result.get() 时才会等待异步任务的完成并获取结果。
1.3、启动策略
std::launch 是一个枚举,决定任务的执行方式。
- std::launch::async:表示任务应该在独立的线程中异步执行。也就是说,当调用 std::async(std::launch::async, func) 时,func 会在一个新的线程中执行。
- std::launch::deferred:表示任务会被延迟到调用 future.get() 或 future.wait() 时才执行。即任务不会在调用 std::async 时立即启动,而是在获取结果时才启动。
2、std::packaged_task
2.1、std::packaged_task的基本语法
std::packaged_task 是 C++11 中的另一项异步操作工具,它封装了一个可调用对象,并提供一个 std::future 来获取异步操作的结果。与 std::async 不同,std::packaged_task 并不自动启动任务,调用者需要手动执行该任务。基本语法如下:
cpp
template <typename F>
class std::packaged_task;
- F: 一个可调用对象的类型(例如函数指针、lambda 表达式等)。
- std::packaged_task 封装了一个任务,并且提供了 get_future() 方法来获取对应的 std::future 对象。
2.2、使用例子
cpp
#include <iostream>
#include <future>
#include <thread>
int task(int x, int y) {
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时任务
return x + y;
}
int main() {
// 创建 packaged_task 对象
std::packaged_task<int(int, int)> task_obj(task);
// 获取与 packaged_task 关联的 future 对象
std::future<int> result = task_obj.get_future();
// 启动新线程来执行任务
std::thread t(std::move(task_obj), 5, 3);
// 在等待异步结果的同时,可以做其他工作
std::cout << "Doing other work in main thread..." << std::endl;
// 获取异步任务结果,get() 会阻塞直到任务完成
int sum = result.get();
std::cout << "Task result: " << sum << std::endl;
// 等待线程结束
t.join();
return 0;
}
我们使用 std::packaged_task 封装了一个任务 task,然后调用 get_future() 获取一个 std::future 对象。任务并不会自动执行,因此我们需要显式地创建一个线程来执行这个任务。最后,我们通过 result.get() 获取结果,get() 会阻塞直到任务完成。
2.3、启动策略
std::async 自动处理线程的创建和管理,而 std::packaged_task 则要求手动创建线程并执行任务 。std::async 可以选择任务延迟执行(通过 std::launch::deferred),而 std::packaged_task 始终会在调用时被启动。
3、std::async 和 std::packaged_task 对比
特性 | std::async | std::packaged_task |
---|---|---|
任务启动时机 | 自动启动,可以选择异步执行或延迟执行 | 任务需要手动启动(通过线程或其他方式) |
线程管理 | 自动管理线程 | 需要显式创建线程来执行任务 |
返回结果 | 返回一个 std::future 对象,直接可以获取结果 | 返回一个 std::future 对象,需要通过线程来执行任务 |
使用场景 | 简单的异步任务,适用于无需控制线程生命周期的场景 | 需要显式控制任务执行时机和线程的场景 |
启动策略 | std::launch::async 或 std::launch::deferred | 不支持延迟执行,只能同步启动 |
4、使用场景
4.1、std::async 的使用场景
- 当你只关心任务的异步执行,而不需要显式控制线程时,std::async 是一个非常方便的选择。
- 它适用于简单的并发任务,且希望任务自动执行,不需要管理线程的生命周期。
4.2、std::packaged_task 的使用场景
- 当你需要手动控制任务的执行时,例如当你希望任务在某个特定时刻或者通过某种特定的方式(如通过线程池)来启动时,std::packaged_task 会是一个合适的选择。
- 它适用于需要与线程池、事件驱动等复杂并发机制一起使用的场景。