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

在C++中执行异步任务,核心思路是让某个操作在后台线程中独立运行,而当前线程可以继续执行其他工作,待需要结果时再获取。std::async是C++标准库提供的一个高级工具,它极大简化了异步编程的复杂性,允许你启动一个函数,并在未来某个时刻获取其结果,而无需直接管理线程。
std::async提供了一种方便的方式来异步执行一个函数或可调用对象,并返回一个std::future对象,通过这个std::future,我们可以获取异步任务的返回值或者捕获可能发生的异常。
它的基本用法是这样的:
#include <iostream>
#include <future>
#include <thread>
#include <chrono>
// 一个模拟耗时操作的函数
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<int> 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<int> 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<int> 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密集型(比如复杂的计算、图像处理)。如果这些操作都按部就班地在主线程中执行,那么用户界面就会卡死,服务器响应会变慢,整个系统会显得非常迟钝。
具体来说,异步任务解决了以下几个核心痛点:
std::thread提供了创建线程的能力,但要正确地管理线程的生命周期、传递参数、获取返回值、处理异常以及进行线程间同步,会涉及到std::promise、std::packaged_task、std::mutex、std::condition_variable等一系列工具,这无疑增加了开发的复杂度和出错的风险。std::async在很多场景下,将这些底层细节抽象化,只通过一个std::future就解决了参数传递、结果获取和异常传播的问题,大大降低了异步编程的门槛。对我而言,异步编程不仅仅是一种技术手段,它更像是一种思维模式的转变。它鼓励我们跳出线性的、顺序的思考框架,去思考如何将任务分解、并行,从而让程序更“活泼”,更具响应性。
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()调用处。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的抽象无法满足你的需求时,例如:
这时,才应该考虑使用std::thread,并准备好处理所有相关的线程管理、同步和通信问题。简单来说,std::async是你的“日常工具”,而std::thread则是“高级定制工具”。
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::future<void> future_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 secondsstd::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<int> fut = std::async(std::launch::async, my_function, args);
合理管理std::future的生命周期:将std::future存储在适当的作用域或数据结构中,确保它在任务完成之前不会被意外销毁。如果任务的结果在程序的后期才需要,那么std::future可能需要作为成员变量或通过智能指针管理。
延迟get()调用:在启动异步任务后,让主线程尽可能地执行其他有用的工作,直到确实需要异步任务的结果时再调用get()。这最大化了并行执行的效益。
std::future<int> fut = std::async(std::launch::async, complex_calculation); // 主线程执行其他独立的工作 do_other_stuff(); // 现在需要结果了 int result = fut.get();
利用wait_for()进行非阻塞等待:如果你不想阻塞主线程,但又想知道任务是否完成,可以使用std::future::wait_for()。
std::future<int> 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<int> 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以上就是如何在C++中执行异步任务_C++异步编程与std::async的详细内容,更多请关注php中文网其它相关文章!
编程怎么学习?编程怎么入门?编程在哪学?编程怎么学才快?不用担心,这里为大家提供了编程速学教程(入门课程),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号