std::async通过返回std::future实现异步任务执行,支持std::launch::async(强制新线程)和std::launch::deferred(延迟调用),避免默认策略的不确定性;其封装了线程管理、结果获取与异常传播,相比std::thread更简洁安全,适用于一次性任务,但需注意future生命周期导致的隐式阻塞及及时处理异常。

在C++中执行异步任务,核心思路是让某个操作在后台线程中独立运行,而当前线程可以继续执行其他工作,待需要结果时再获取。std::async是C++标准库提供的一个高级工具,它极大简化了异步编程的复杂性,允许你启动一个函数,并在未来某个时刻获取其结果,而无需直接管理线程。
解决方案
std::async提供了一种方便的方式来异步执行一个函数或可调用对象,并返回一个std::future对象,通过这个std::future,我们可以获取异步任务的返回值或者捕获可能发生的异常。
它的基本用法是这样的:
#include#include #include #include // 一个模拟耗时操作的函数 int calculate_something(int input) { std::cout << "Task started with input: " << input << " on thread: " << std::this_thread::get_id() << std::endl; std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时 std::cout << "Task finished on thread: " << std::this_thread::get_id() << std::endl; return input * 2; } int main() { std::cout << "Main thread ID: " << std::this_thread::get_id() << std::endl; // 1. 使用默认策略(std::launch::async | std::launch::deferred) // 这种策略下,任务可能在新线程中运行,也可能在get()时同步运行。 // 这也是一个常见的“陷阱”,因为行为不确定。 std::future future_result_default = std::async(calculate_something, 10); std::cout << "Task launched with default policy. Main thread continues..." << std::endl; // 此时主线程可以做其他事情... std::this_thread::sleep_for(std::chrono::milliseconds(500)); std::cout << "Main thread doing something else." << std::endl; int result_default = future_result_default.get(); // 阻塞直到任务完成并获取结果 std::cout << "Result from default policy: " << result_default << std::endl; std::cout << "------------------------------------" << std::endl; // 2. 明确指定 std::launch::async 策略 // 强制任务在新线程中执行。这是大多数人期望的异步行为。 std::future future_result_async = std::async(std::launch::async, calculate_something, 20); std::cout << "Task launched with async policy. Main thread continues..." << std::endl; // 此时主线程可以做其他事情... std::this_thread::sleep_for(std::chrono::milliseconds(500)); std::cout << "Main thread doing something else again." << std::endl; int result_async = future_result_async.get(); // 阻塞直到任务完成并获取结果 std::cout << "Result from async policy: " << result_async << std::endl; std::cout << "------------------------------------" << std::endl; // 3. 明确指定 std::launch::deferred 策略 // 任务不会立即执行,而是在future的get()或wait()方法被调用时,在调用线程中同步执行。 // 这更像是“惰性求值”。 std::future future_result_deferred = std::async(std::launch::deferred, calculate_something, 30); std::cout << "Task launched with deferred policy. Main thread continues, but task is not running yet." << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(500)); std::cout << "Main thread doing something else for deferred task." << std::endl; // 此时,deferred任务才真正开始执行 int result_deferred = future_result_deferred.get(); std::cout << "Result from deferred policy: " << result_deferred << std::endl; return 0; }
这段代码展示了std::async的三种常见用法,尤其是通过std::launch策略控制任务的执行方式。get()方法会阻塞当前线程直到异步任务完成并返回结果。如果异步任务抛出异常,get()也会重新抛出该异常。
立即学习“C++免费学习笔记(深入)”;
为什么我们需要异步任务?它解决了哪些痛点?
在我看来,异步任务的存在,很大程度上是为了解决“等待”的困境。在现代软件开发中,无论是桌面应用、服务器后端还是高性能计算,我们都不可避免地会遇到一些耗时操作。这些操作可能是I/O密集型(比如读写文件、网络请求),也可能是CPU密集型(比如复杂的计算、图像处理)。如果这些操作都按部就班地在主线程中执行,那么用户界面就会卡死,服务器响应会变慢,整个系统会显得非常迟钝。
具体来说,异步任务解决了以下几个核心痛点:
- 用户体验(UI响应性):这是最直观的。想象一下,你点击一个按钮,然后程序“假死”几秒钟,直到后台计算完成。这体验简直糟糕透顶。异步任务可以将耗时操作扔到后台线程,让主线程(尤其是UI线程)保持活跃,随时响应用户的输入,从而提升用户满意度。
- 资源利用率:现代处理器大多是多核的。如果你的程序只有一个线程,那么在执行CPU密集型任务时,其他核心就处于闲置状态,这无疑是巨大的浪费。异步任务允许你将不同的计算任务分发到不同的核心上并行执行,从而充分利用多核处理器的计算能力,显著缩短总的执行时间。对于I/O密集型任务,当一个线程在等待I/O完成时,另一个线程可以继续处理其他事情,避免了CPU空转。
- 系统吞吐量:在服务器端应用中,如果每个请求都同步处理,那么一个慢请求就会阻塞整个服务器,导致其他请求也无法及时响应。通过异步处理,服务器可以同时处理多个请求,当一个请求在等待数据库或网络响应时,可以切换到处理另一个请求,从而提高系统的并发处理能力和吞吐量。
-
简化复杂性(相对于裸线程):虽然
std::thread提供了创建线程的能力,但要正确地管理线程的生命周期、传递参数、获取返回值、处理异常以及进行线程间同步,会涉及到std::promise、std::packaged_task、std::mutex、std::condition_variable等一系列工具,这无疑增加了开发的复杂度和出错的风险。std::async在很多场景下,将这些底层细节抽象化,只通过一个std::future就解决了参数传递、结果获取和异常传播的问题,大大降低了异步编程的门槛。
对我而言,异步编程不仅仅是一种技术手段,它更像是一种思维模式的转变。它鼓励我们跳出线性的、顺序的思考框架,去思考如何将任务分解、并行,从而让程序更“活泼”,更具响应性。
std::async与std::thread有什么不同?何时选择它们?
std::async和std::thread都是C++中实现并发的工具,但它们的设计理念和适用场景有着显著的区别。理解这些差异,对于我们做出正确的选择至关重要。
std::thread:底层、精细控制
std::thread是C++标准库提供的最底层的线程抽象。当你使用std::thread时,你实际上是在直接操作一个操作系统级别的线程。
-
特点:
-
直接控制:你可以完全控制线程的生命周期,包括创建、启动、分离(
detach)或等待其完成(join)。 -
无结果返回机制:
std::thread本身不提供直接的机制来获取线程函数的返回值。如果你需要返回值,通常需要配合std::promise和std::future,或者使用共享数据(如std::atomic、std::mutex保护的变量)。 -
异常处理:线程函数内部抛出的异常不会自动传播到创建线程。你需要手动捕获并传递异常,通常也是通过
std::promise。 -
强制管理:你必须明确调用
join()或detach()来管理线程的生命周期,否则在std::thread对象销毁时会调用std::terminate。
-
直接控制:你可以完全控制线程的生命周期,包括创建、启动、分离(
-
适用场景:
-
构建线程池:当你需要一个固定数量的线程来处理大量任务时,
std::thread是构建线程池的基础。 - 长期运行的服务:例如后台监听服务、日志记录器,这些线程通常需要一直运行,直到程序结束。
- 对线程生命周期有精细控制需求:当你需要手动设置线程属性、优先级,或者需要线程间复杂的同步和通信时。
-
学习和理解并发原语:
std::thread是理解C++并发模型的基础。
-
构建线程池:当你需要一个固定数量的线程来处理大量任务时,
std::async:高层、任务导向
std::async是一个更高层次的抽象,它更侧重于“任务”而不是“线程”。它将线程的创建、管理、结果获取和异常传播等细节封装起来。
-
特点:
-
任务导向:你给它一个可调用对象,它负责在后台执行,并返回一个
std::future来代表未来的结果。 -
自动管理:
std::async可以根据策略(std::launch::async或std::launch::deferred)自动决定是在新线程中运行任务,还是在调用get()或wait()时同步运行。 -
结果与异常:通过
std::future,你可以轻松地获取任务的返回值,并且任务中抛出的异常也会被捕获并传播到get()调用处。 -
RAII风格:
std::future对象的析构函数会阻塞直到任务完成,这在一定程度上避免了std::thread未join或detach导致的std::terminate问题,但同时也可能带来意想不到的阻塞。
-
任务导向:你给它一个可调用对象,它负责在后台执行,并返回一个
-
适用场景:
-
一次性任务:当你只需要将一个函数或操作放到后台执行,并最终获取其结果时,
std::async是理想选择。 -
简化代码:对于简单的异步操作,
std::async比手动使用std::thread配合std::promise和std::future要简洁得多。 - 不需要精细控制线程:如果你不关心任务具体在哪一个线程上运行,也不需要对线程的生命周期进行复杂的管理。
-
惰性求值:当使用
std::launch::deferred策略时,可以实现类似函数式编程中的惰性求值。
-
一次性任务:当你只需要将一个函数或操作放到后台执行,并最终获取其结果时,
何时选择?
我的经验是,优先考虑std::async。它更符合现代C++的RAII和抽象原则,能够有效减少并发编程的复杂性。只有当std::async的抽象无法满足你的需求时,例如:
AJAX即“Asynchronous Javascript And XML”(异步JavaScript和XML),是指一种创建交互式网页应用的网页开发技术。它不是新的编程语言,而是一种使用现有标准的新方法,最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容,不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。《php中级教程之ajax技术》带你快速
- 你需要一个长期运行的、可控的后台线程。
- 你需要构建一个自定义的线程池。
- 你需要对线程的调度、优先级、亲和性等有非常细致的控制。
- 你正在处理一个非常底层的系统级并发问题。
这时,才应该考虑使用std::thread,并准备好处理所有相关的线程管理、同步和通信问题。简单来说,std::async是你的“日常工具”,而std::thread则是“高级定制工具”。
使用std::async时常见的陷阱和最佳实践
std::async虽然方便,但它也有一些容易让人掉坑的地方。理解这些,能帮助我们更稳健地编写异步代码。
常见的陷阱:
-
默认启动策略的非确定性行为:
- 这是
std::async最常见的“坑”。当你像这样调用时:std::async(calculate_something, 10),你实际上是使用了默认的启动策略std::launch::async | std::launch::deferred。这意味着运行时系统可以自由选择是在一个新线程中异步执行任务,还是延迟到future_result.get()或future_result.wait()被调用时才在当前线程同步执行。 -
问题:这种不确定性可能导致你期望的并行性并未发生,或者在某些情况下,你可能遇到死锁(如果
deferred任务试图获取一个已经被主线程持有的锁)。性能表现也可能因此变得难以预测。 -
我的看法:如果你明确需要并行执行,总是明确指定
std::launch::async。如果你需要惰性求值,就明确指定std::launch::deferred。不要依赖默认策略,它会给你带来很多困惑。
- 这是
-
std::future的生命周期导致的阻塞:- 如果一个
std::future对象被创建,但你没有调用它的get()或wait()方法,并且这个std::future对象在任务完成之前被销毁了(例如,它是一个局部变量,函数返回了),那么std::future的析构函数会阻塞当前线程,直到它所关联的异步任务完成。 -
问题:这可能会导致程序在不期望的地方阻塞,甚至影响性能。你可能认为任务在后台运行,但实际上因为
future的销毁,主线程被强制等待。 -
示例:
void run_async_task() { // future_obj是一个局部变量 std::futurefuture_obj = std::async(std::launch::async, []{ std::this_thread::sleep_for(std::chrono::seconds(5)); std::cout << "Async task done." << std::endl; }); std::cout << "Async task launched, future_obj will be destroyed soon." << std::endl; // future_obj 在这里析构,会阻塞当前线程5秒 } // future_obj 离开作用域 // main thread will block here for 5 seconds -
解决方案:确保
std::future的生命周期足够长,或者在不需要结果时,显式地调用wait()或get()来处理任务的完成。如果不需要等待,可以考虑detach()一个std::thread,但那又回到了std::thread的复杂性。
- 如果一个
-
过早调用
get()或wait():- 异步任务的意义在于“异步”,即主线程可以继续做其他事情。如果你在启动任务后立即调用
future.get()或future.wait(),那么主线程就会立即阻塞,等待异步任务完成,这实际上就变成了同步执行,失去了异步的优势。 - 问题:这会抵消异步带来的性能提升和响应性改善。
-
解决方案:只有当你确实需要异步任务的结果时,才调用
get()。在此之前,让主线程处理其他可并行执行的任务。如果只是想检查任务是否完成而不阻塞,可以使用future.wait_for()或future.wait_until()。
- 异步任务的意义在于“异步”,即主线程可以继续做其他事情。如果你在启动任务后立即调用
-
异常处理的遗漏:
- 异步任务中抛出的异常会被存储在
std::future中。如果你不调用get(),这个异常就永远不会被重新抛出,也不会被处理。 - 问题:潜在的错误可能被掩盖,导致程序状态不一致或崩溃。
-
解决方案:总是在
get()调用处使用try-catch块来处理异步任务可能抛出的异常。
- 异步任务中抛出的异常会被存储在
最佳实践:
-
明确指定启动策略:如前所述,如果你需要真正的并行执行,总是使用
std::launch::async。如果你想要惰性求值,使用std::launch::deferred。避免默认策略带来的不确定性。std::future
fut = std::async(std::launch::async, my_function, args); 合理管理
std::future的生命周期:将std::future存储在适当的作用域或数据结构中,确保它在任务完成之前不会被意外销毁。如果任务的结果在程序的后期才需要,那么std::future可能需要作为成员变量或通过智能指针管理。-
延迟
get()调用:在启动异步任务后,让主线程尽可能地执行其他有用的工作,直到确实需要异步任务的结果时再调用get()。这最大化了并行执行的效益。std::future
fut = std::async(std::launch::async, complex_calculation); // 主线程执行其他独立的工作 do_other_stuff(); // 现在需要结果了 int result = fut.get(); -
利用
wait_for()进行非阻塞等待:如果你不想阻塞主线程,但又想知道任务是否完成,可以使用std::future::wait_for()。std::future
fut = std::async(std::launch::async, long_running_task); std::chrono::milliseconds span(100); while (fut.wait_for(span) == std::future_status::timeout) { std::cout << "Task not ready yet, doing something else..." << std::endl; // 主线程可以继续做其他事情 } if (fut.valid()) { int result = fut.get(); std::cout << "Task finished with result: " << result << std::endl; } -
妥善处理异常:在调用
get()时,始终考虑并处理可能从异步任务中传播出来的异常。std::future
fut = std::async(std::launch::async, task_that_might_throw); try { int result = fut.get(); std::cout << "Task completed successfully: " << result << std::endl; } catch (const std::exception& e) { std::cerr << "Task threw an exception: " << e.what() << std::endl










