C++ async使用陷阱:异步执行与阻塞分析 | 实战经验

C++ async使用陷阱:异步执行与阻塞分析 #

引言 #

C++的async提供了便捷的异步编程方式,但其使用中存在一些容易被忽视的陷阱。 一般人可能都知道 C++ 异步操作有 async 这个东西。但不知道大家是否注意过,其实它有两个坑:

  1. 它不一定真的会异步执行
  2. 它有可能会阻塞

下面是 async 具体的介绍:

async 是比 future,packaged_task,promise 更高级的东西,它是基于任务的异步操作。

通过 async 可以直接创建异步的任务,返回的结果会保存在 future 中,不需要像 packaged_task 和 promise 那么麻烦。

关于线程操作可以优先使用 async,看一段使用代码:

#include <functional>
#include <future>
#include <iostream>
#include <thread>

using namespace std;

int func(int in) { return in + 1; }

int main() {
    auto res = std::async(func, 5);
    // res.wait();
    cout << res.get() << endl; // 阻塞直到函数返回
    return 0;
}

使用 async 异步执行函数是不是方便多啦。

async 具体语法如下:

async(std::launch::async | std::launch::deferred, func, args...);

第一个参数是创建策略:

  • std::launch::async 表示任务执行在另一线程
  • std::launch::deferred 表示延迟执行任务,调用 get 或者 wait 时才会执行,不会创建线程,惰性执行在当前线程。

如果不明确指定创建策略,以上两个都不是 async 的默认策略,而是 undefined,它是一个基于任务的程序设计,内部有一个调度器(线程池),会根据实际情况决定采用哪种策略。

若从 std::async 获得的 std::future 未被移动或绑定到引用,则在完整表达式结尾。

注意:std::future 的析构函数将阻塞直至异步计算完成,实际上相当于同步操作:

std::async(std::launch::async, []{ f(); }); // 临时量的析构函数等待 f()
std::async(std::launch::async, []{ g(); }); // f() 完成前不开始

注意:关于 async 启动策略这里网上和各种书籍介绍的五花八门,这里会以 cppreference 为主。

有时候我们如果想真正执行异步操作可以对 async 进行封装,强制使用 std::launch::async 策略来调用 async。

template <typename F, typename... Args>
inline auto ReallyAsync(F&& f, Args&&... params) {
    return std::async(std::launch::async, std::forward<F>(f), std::forward<Args>(params)...);
}

参考资料

https://en.cppreference.com/w/cpp/thread/async