目录
🌞1. std::async 简介
🌞2. 问题梳理
🌊2.1 std::async 到 future get 直接调用会如何抛异常
🌊2.2 std::async 如果通过劫持让 new 内存不够,有没有可能抛异常
🌊2.3 std::async 如果系统线程不够有没有可能异常
🌞1. std::async 简介
std::async
是 C++11 标准库中用于异步执行的函数,会返回一个 std::future
对象,以获取函数的执行结果。可用其在新线程中执行函数,也可以在当前线程中执行。std::async
的函数声明形式通常如下:
template <typename F, typename... Args>
std::future<typename std::result_of<F(Args...)>::type> std::async(std::launch policy, F&& f, Args&&... args);
说明:
template <typename F, typename... Args>
:函数模板声明。F
是要执行的函数类型,Args...
是函数参数类型的模板参数包(variadic template parameter)。这意味着函数可以接受任意数量的参数。
std::future<typename std::result_of<F(Args...)>::type>
:这是std::async
函数的返回类型。它是一个std::future
对象,包装了函数F
的返回类型。std::future
允许在未来的某个时间点获取函数的执行结果。
std::async(std::launch policy, F&& f, Args&&... args)
:这是函数std::async
的声明。它接受三个参数:
policy
:std::launch
类型的参数,表示函数执行的策略。
可以是std::launch::async
(在新线程中异步执行)或std::launch::deferred
(延迟执行,在调用std::future::get()
或std::future::wait()
时执行)。f
:通用引用(universal reference),表示要执行的函数对象。通用引用允许f
接受任意类型的参数。args
:这是函数f
的参数列表。可以是零个或多个参数。这个函数的作用是根据给定的执行策略异步执行函数
f
,并返回一个std::future
对象,可用来等待函数的执行完成并获取函数的结果。
注意:
std::async
的行为受到执行策略参数【
std::launch
类型的参数】
的影响,可能在调用时立即执行,也可能延迟到 std::future::get()
或 std::future::wait()
被调用时才执行。
🌞2. 问题梳理
🌊2.1 std::async 到 future get 直接调用会如何抛异常
std::async
到 std::future::get
直接调用会抛出异常,主要有两种情况:
- 函数对象抛出异常。
- 如果使用
std::launch::async
策略,并在调用std::future::get
之前函数执行抛出了异常,这种情况下会导致std::future::get
抛出std::future_error
异常。
【示例1】函数对象抛出异常
#include <iostream>
#include <future>// 抛出异常
void task1() {throw std::runtime_error("An error occurred in task1()");
}int main() {try {// 使用 std::async 启动一个异步任务auto future1 = std::async(std::launch::async, task1);// 等待异步任务的完成并获取结果future1.get(); // 这里会抛出异常} catch (const std::exception& e) {std::cerr << "Caught exception: " << e.what() << std::endl;}return 0;
}//输出内容:
Caught exception:An error occurred in task1()
该示例中,task1
函数会抛出异常。当调用 future1.get()
时,如果 task1
函数抛出异常,std::future::get
也会抛出异常。
【示例2】使用 std::launch::async 策略并在函数执行前抛出异常
#include <iostream>
#include <future>
#include <thread>
#include <chrono>// 在函数执行前抛出异常的函数
void task2() {// 人为延迟,增加在调用 std::future::get 前抛出异常的机会std::this_thread::sleep_for(std::chrono::milliseconds(100));throw std::runtime_error("An error occurred in task2()");
}int main() {try {// 使用 std::async 启动一个异步任务,使用 std::launch::async 策略auto future2 = std::async(std::launch::async, task2);// 在get函数执行前抛出异常throw std::runtime_error("An error occurred before calling future2.get()");// 等待异步任务的完成并获取结果future2.get(); // 这里会抛出 std::future_error 异常} catch (const std::exception& e) {std::cerr << "Caught exception: " << e.what() << std::endl;}return 0;
}//输出内容:
Caught exception: An error occurred before calling future2.get()
该示例中,task2
函数会在std::future::get
函数执行前抛出异常。
在 main
函数中,虽然调用 future2.get()
前手动抛出了异常,但是由于使用了 std::launch::async
策略,task2
函数会在新线程中执行【std::future::get()
调用则在当前线程(即主线程)中执行】。因此,即使在主线程中抛出了异常,新线程中的任务函数也会继续执行:std::future::get
会等待 task2
函数执行完成【含加入的延时:100毫秒】,然后抛出 std::future_error
异常,说明在获取结果之前已经发生异常。
🌊2.2 std::async 如果通过劫持让 new 内存不够,有没有可能抛异常
std::async
不会直接抛出异常来处理内存不足的情况。在 C++ 中,当 new
操作符无法分配所需的内存时,会抛出 std::bad_alloc
异常。但std::async
不会直接抛出该异常。
在 std::async
中,任务可能在一个新线程中执行,也可能在当前线程中执行。如果任务在新线程中执行,并且在该新线程中发生了内存分配失败,那么系统会终止整个程序,而不是将异常传递回调用 std::async
的地方。【这是因为线程的异常不能跨线程传递】
这是因为C++的异常处理机制不能跨线程传播。当一个异常在一个线程中被抛出,而没有被捕获时,它会导致这个线程终止。如果异常发生在
std::async
创建的新线程中,并且在那里没有被捕获,那么整个线程会终止,但异常不会被传递回调用std::async
的线程。所以,虽然劫持
new
可以模拟内存不足的情况,但由于异常处理机制的限制,std::async
并不能捕获由于新线程中的内存分配失败而导致的异常。
所以,如果在 std::async
内部发生了内存分配失败,程序通常会终止并可能会生成错误报告,而不是抛出异常到 std::async
的调用者。因此,对于内存不足的情况,最好在程序中进行适当的内存管理和异常处理,而不是依赖于 std::async
来处理此类问题。
【示例1】系统内存不足导致异常
#include <iostream>
#include <future>
#include <vector>
#include <cstdlib>void task() {// 尝试分配大量内存,可能导致内存不足std::vector<int> v(1000000000); // 尝试分配 4 GB 的内存
}int main() {try {// 尝试启动一个异步任务auto future = std::async(std::launch::async, task);// 等待异步任务的完成并获取结果future.get();} catch (const std::exception& e) {std::cerr << "Caught exception: " << e.what() << std::endl;}return 0;
}//输出内容:
Caught exception:bad allocation
该示例中,task
函数尝试分配大量内存。如果系统内存不足,std::vector
的构造函数将抛出 std::bad_alloc
异常。由于这个异常没有在 task
函数内部被捕获,因此异常会传播到 std::async
调用处,进而抛出 std::system_error
异常。
【示例2】劫持 new 让 new 内存不够抛异常
#include <iostream>
#include <future>void* operator new(size_t size) {std::cout << "Overloaded new called with size: " << size << std::endl;// 模拟内存不足的情况,分配失败throw std::bad_alloc();
}int main() {try {// 调用std::async,启动一个异步任务auto future = std::async(std::launch::async, [](){// 在这个异步任务中进行一些内存分配操作int* ptr = new int[100000000]; // 尝试分配非常大的内存delete[] ptr;});// 获取异步任务的结果future.get();} catch(const std::bad_alloc& e) {// 捕获异常并输出错误信息std::cerr << "Caught bad_alloc exception: " << e.what() << std::endl;}return 0;
}//输出内容:
Overloaded new called with size: 176
Caught bad_alloc exception:bad allocation
该示例中,重载 new
运算符,使其抛出 std::bad_alloc
异常,而不是实际分配内存。在 task
函数内部,尝试分配大量内存,并捕获了 std::bad_alloc
异常。由于 new
运算符的劫持,内存分配失败时会抛出异常。这个异常会在 std::async
调用处被捕获。
🌊2.3 std::async 如果系统线程不够有没有可能异常
这种情况下,std::async
可能会抛出 std::system_error
异常。
在使用 std::async
时,如果系统线程不够,可能会导致无法启动新线程而引发异常。这通常不是由于内存不足引起的,而是由于达到了系统对同时运行线程数量的限制。
【示例】系统线程不够抛异常
#include <iostream>
#include <future>
#include <vector>
#include <thread>
#include <chrono>void task() {// 模拟一个耗时的任务std::this_thread::sleep_for(std::chrono::seconds(2));std::cout << "Task executed in thread: " << std::this_thread::get_id() << std::endl;
}int main() {try {std::vector<std::future<void>> futures;// 启动多个异步任务for (int i = 0; i < 10000000000000; ++i) {futures.push_back(std::async(std::launch::async, task));}// 等待所有异步任务完成for (auto& future : futures) {future.get();}} catch(const std::system_error& e) {// 捕获系统错误异常并输出错误信息std::cerr << "Caught system_error exception: " << e.what() << std::endl;}return 0;
}
该示例启动了多个异步任务,每个任务执行一个模拟的耗时操作。如果系统没有足够的线程资源来启动这些线程,会抛出 std::system_error
异常。