std::async 是 C++11 引入的一个工具函数,它主要用于在程序中创建 异步任务、延迟任务。本篇文章将通过设计的 6 个示例程序来展示如何利用 std::async
实现异步任务与延迟任务。
1. 异步任务
一个任务或操作可以独立于主线程(或主任务)运行,主线程无需等待任务完成便可继续执行其他工作。当异步任务完成时,可以通过某种机制(如 std::future
)获取其结果。
1.1 异步任务的基本使用
这一小节主要介绍如何创建异步任务,以及如何获得异步任务的返回结果。这里需要注意的是:std::async 函数的第一个参数如果没有指定的话,那么默认任务是异步任务还是延迟任务取决于具体实现。
#if 1 #define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<thread> #include<chrono> #include<future> int task(int number) { std::this_thread::sleep_for(std::chrono::seconds(2)); return 100 + number; } void test() { // 下面代码执行完成之后,异步任务就开始执行 std::future<int> fut = std::async(std::launch::async, task, 10); std::cout << "任务开始执行..." << std::endl; try { // 阻塞等待任务函数执行结束,并得到结果 int ret = fut.get(); std::cout << "ret = " << ret << std::endl; } catch (const std::exception& ex) { std::cout << ex.what() << std::endl; } std::cout << "程序结束" << std::endl; } int main() { test(); return EXIT_SUCCESS; } #endif
1.2 没有返回值任务场景
wait
函数会阻塞等待任务函数执行结束(正常结束、异常结束),并不关心返回值。注意:如果任务函数抛出异常,wait 函数不会再抛出,而是保存到 std::future 对象内部,当调用 get 函数时会抛出异常。
案例:A B 两个异步任务分别执行将不同的内容写入到不同的文件中,然后由主线程将两个文件的内容合并到第三个文件中。
#if 1 #define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<thread> #include<chrono> #include<future> #include<fstream> void write_file(std::string filename, std::string content) { std::ofstream ofs(filename); // 等待 2 秒,表示耗时任务 std::this_thread::sleep_for(std::chrono::seconds(2)); ofs << content << std::endl; ofs.close(); } void test() { // 下面代码执行完成之后,线程函数就开始执行 std::future<void> fut1 = std::async(std::launch::async, write_file, "demo1.txt", "hello world"); std::future<void> fut2 = std::async(std::launch::async, write_file, "demo2.txt", "hello python"); std::cout << "任务开始执行..." << std::endl; // 等待异步任务执行完毕 fut1.wait(); fut2.wait(); // 合并文件 std::ifstream ifs1("demo1.txt"); std::ifstream ifs2("demo2.txt"); std::ofstream ofs("demo.txt"); std::copy(std::istreambuf_iterator<char>(ifs1), std::istreambuf_iterator<char>(), std::ostreambuf_iterator<char>(ofs)); std::copy(std::istreambuf_iterator<char>(ifs2), std::istreambuf_iterator<char>(), std::ostreambuf_iterator<char>(ofs)); ifs1.close(); ifs2.close(); ofs.close(); std::cout << "程序结束" << std::endl; } int main() { test(); return EXIT_SUCCESS; } #endif
1.3 非阻塞获得任务结果
get 和 wait 都是阻塞等待任务结束。也可以通过 wait_for、wait_until 来非阻塞方式等待异步任务结果。
- wait_for 会在指定时间内等待任务状态变化,如果任务没有结束,返回 timeout,否则返回 ready
- wait_until 则表示等待到某个时间点,如果任务状态没有发生变化,则返回 timeout,否则返回 ready
我们可以通过不停的检查任务的状态,来实现非阻塞的方式获得任务结果。
#if 1 #define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<thread> #include<chrono> #include<future> int calculate(int number) { int result = 0; for (int i = 0; i < number; ++i) { std::this_thread::sleep_for(std::chrono::seconds(1)); result += 10; } return result; } void test() { std::future<int> fut = std::async(std::launch::async, calculate, 5); std::cout << "任务开始执行..." << std::endl; while (true) { std::future_status status = fut.wait_for(std::chrono::seconds(1)); // std::future_status status = fut.wait_until(std::chrono::steady_clock().now() + std::chrono::seconds(2)); if (status == std::future_status::ready) { std::cout << "子线程结果:" << fut.get() << std::endl; break; } if (status == std::future_status::timeout) { std::cout << "子任务尚未执行完毕,做会其他的事情..." << std::endl; } } } int main() { test(); return EXIT_SUCCESS; } #endif
2. 延迟任务
将任务的执行推迟,直到确实需要时才开始执行。这种方式通常用于系统负载较高时,任务的结果又不立即需要的场景,帮助系统优化资源使用,提升整体性能。
2.1 延迟任务的基本使用
这一小结主要通过两个案例来介绍如何创建延迟任务、如何触发延迟任务执行、以及如何通过阻塞和非阻塞两种方式获得任务结果。
#if 1 #define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<thread> #include<chrono> #include<future> int task(int number) { std::this_thread::sleep_for(std::chrono::seconds(8)); return 100 + number; } void test01() { // 任务不会马上执行 std::future<int> fut = std::async(std::launch::deferred, task, 10); // 当调用 get、wait 函数时,触发任务函数执行 int ret = fut.get(); // std::cout << "ret = " << ret << std::endl; // fut.wait(); } void test02() { std::future<int> fut = std::async(std::launch::deferred, task, 10); bool is_start = false; while (true) { // 当延迟任务尚未执行前,wait_for 函数不会阻塞,会立即返回 std::future_status status = fut.wait_for(std::chrono::seconds(1)); if (status == std::future_status::deferred && !is_start) { std::cout << "任务尚未开始执行..." << std::endl; std::thread([&fut]() { fut.wait(); }).detach(); is_start = true; } if (status == std::future_status::timeout) { std::cout << "任务正在执行..." << std::endl; } if (status == std::future_status::ready) { std::cout << "任务执行完毕,执行结果是:" << fut.get() << std::endl; break; } } } int main() { test02(); return EXIT_SUCCESS; } #endif
2.2 独享和共享任务结果
我们一直使用 std::future
来获得任务的结果,但是它适合一次性获取结果,多次获取会导致异常。当任务需要在多个地方、或者多次获取结果时,需要使用 std::shared_future
来代替 std::future
。
- 我们可以在调用
std::async
函数时,直接使用std::shared_future
来承接返回值。 - 也可以调用
std::future
的share
函数来返回一个std::shared_future
类型的对象。
#if 1 #define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<thread> #include<chrono> #include<future> void test() { std::future<int> fut = std::async(std::launch::deferred, []() { std::cout << "延迟任务开始执行..." << std::endl; std::this_thread::sleep_for(std::chrono::seconds(2)); return 100; }); #if 0 // std::future 只能获取一次结果,重复获取结果将会抛出异常 // std::shared_future 可以重复获取结果 int ret = fut.get(); ret = fut.get(); #else std::shared_future<int> shared_fut = fut.share(); int ret = shared_fut.get(); ret = shared_fut.get(); #endif } int main() { test(); return EXIT_SUCCESS; } #endif
2.3 延迟任务的案例理解
假设:用户在网页查看某张图片,但是只有处理过的图片才能够展示给用户。我们可以:
- 集中时间处理所有图片,这样的话,会在一段时间时间内占用大量的系统资源,会影响系统性能
- 按需处理图片,用户请求那张就开个线程处理哪张,处理过的图像直接返回,剩余未处理的可以在系统空闲时再集中处理
显然,在系统资源有限的情况下,第二种更合适一些。第二种就需要用到延时任务。
#if 1 #define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<thread> #include<chrono> #include<future> #include<map> std::string data_analysis(std::string path) { std::this_thread::sleep_for(std::chrono::seconds(10)); return path; } void test() { std::map<std::string, std::tuple<bool, std::shared_future<std::string>>> tasks; // 添加延迟图像处理任务 tasks.insert({ "a.png", {false, std::async(std::launch::deferred, data_analysis, "a.png")} }); tasks.insert({ "b.png", {false, std::async(std::launch::deferred, data_analysis, "b.png")} }); tasks.insert({ "c.png", {false, std::async(std::launch::deferred, data_analysis, "c.png")} }); // 用户查看图像 std::string name; while (true) { std::cout << "请输入查看的图像:"; std::cin >> name; if (name == "exit") { for (auto& task : tasks) { std::cout << task.first << " " << std::boolalpha << std::get<0>(task.second) << std::endl; } std::cout << "----------------" << std::endl; break; } // 查看图像目前状态 auto& picture = tasks[name]; auto& is_ok = std::get<0>(picture); auto& fut = std::get<1>(picture); if (is_ok) { std::cout << "您要查看的图像是:" << fut.get() << std::endl; continue; } std::future_status status = fut.wait_for(std::chrono::seconds(1)); if (status == std::future_status::deferred) { std::cout << "图像尚未处理, 开始处理图像..." << std::endl; std::thread([&fut]() { fut.wait(); }).detach(); } if (status == std::future_status::timeout) { std::cout << "图像正在处理中" << std::endl; } if (status == std::future_status::ready) { is_ok = true; } std::cout << "----------------" << std::endl; } } int main() { test(); return EXIT_SUCCESS; } #endif
支持一下。