c++11 std::async使用注意

本文详细探讨了C++11中std::async的功能及使用注意事项,包括其启动策略、返回的future对象特性及其潜在的问题。通过示例说明了如何避免析构时的阻塞,并提供了一个替代方案。

    std::async,c++11中提供的异步任务高级抽象,包含在 <future>头文件中,能让你方便的实现异步地执行一个任务

并在需要地时候获取其结果。和直接使用std::thread 相比,有着一些优势:

  1.std::async 返回的future对象,可以方便地等待callable对象执行完成并获取其返回值

  2.能从实现库的一些高级功能中获益,比如线程池等,并大大减少异常的产生。

当然其使用更简洁。但是async存在着一些问题,使用的时候要小心。

1.async的启动策略,两种启动策略 std::launch::async , std::launch::deferred

如果不指定,将采用默认的启动策略,等同于 launch::async | launch::deferred

并且标准中没有规定默认采用哪种策略,有实现库自己决定,测试VS2013中默认方式为async而gcc4.8.2为deferred。

在使用时就要注意。如果为launch::deferred模式,那么永远不会启动新线程去执行,直到你调用 wait 或 get时

才在你的当前线程中去执行callable对象。如果你需要让callable立即启动线程去执行,那么一定要

在调用async时指定launch::async,可以使用一个简单的包装避免忘记:

template <typename F, typename... Args>
inline auto real_async(F&& f, Args&&... args)
  -> std::future<typename std::result_of<F(Args...)>::type>
{
    return std::async(std::launch::async, 
		      std::forward<F>(f), 
		      std::forward<Args>(args)...);
}

2.std::async返回的future对象

以下代码存在一个问题

void work_proc()
{
    vector<int> numbers;
    ...   //do something
    real_async(some_proc,  std::move(numbers));
}

在当前线程中调用了work_proc,希望让单独的一个线程去处理一些不关心结果的耗时的事情,然后

当前线程再去处理一些其他的任务。然而事情不一定如你期待那样。实际上在vs2013上一切表现如

预期,在gcc4.8.2测试,会一直阻塞在work_proc中,直到some_proc执行完成。

在标准库中有说明,在std::future的析构函数中:

~future();
 (since C++11)
   

Releases any shared state. This means

  • if the return object or provider holds the last reference to its shared state, the shared state is destroyed; and
  • the return object or provider gives up its reference to its shared state; and

  • these actions will not block for the shared state to become ready, except that it may block if all of the following are true: the shared state was created by a call to std::async, the shared state is not yet ready, and this was the last reference to the shared state.

注意第三条:在以下三个条件全部满足时future析构可能阻塞

(1)共享状态是由调用std::async时创建的

(2)共享状态还不是ready状态

(3)被析构的当前对象持有着对共享状态的最后一个引用

而work_proc函数中的情况刚好全部满足。然而不幸的是如果你仍然想用async,并且忽略其返回,是没办法避免阻塞的。

但是还是有办法实现相同的目的,使用std::packaged_task和std::thread.

template <typename F, typename... Args>
auto really_async2(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type>
{
    using _Ret = typename std::result_of<F(Args...)>::type;
    auto _func = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
    std::packaged_task<_Ret()> tsk(std::move(_func));
    auto _fut = tsk.get_future();
    std::thread thd(std::move(tsk));
    thd.detach();
    return _fut;
}


### C++ `std::async` 的使用与配置 #### 什么是 `std::async` `std::async` 是 C++11 中引入的一个工具函数,用于启动异步任务。它返回一个 `std::future` 对象,该对象可以用来获取异步任务的结果或状态。 通过调用 `std::async()` 函数,默认情况下会以 `std::launch::async` 方式运行任务[^1]。这意味着任务会在单独的线程上执行。然而,也可以指定其他策略来控制任务的行为。 --- #### 基本语法 以下是 `std::async` 的基本语法: ```cpp template< class Function, class... Args > std::future<typename std::result_of<Function(Args...)>::type> async( std::launch policy, Function&& f, Args&&... args ); ``` 参数解释: - **policy**: 启动策略,可以选择 `std::launch::async` 或 `std::launch::deferred`。 - `std::launch::async`: 创建新线程并立即执行任务。 - `std::launch::deferred`: 将任务延迟到第一次访问结果时才执行。 - **f**: 要执行的任务函数或可调用对象。 - **args**: 提供给任务函数的参数列表。 --- #### 示例代码 下面是一个简单的例子,展示如何使用 `std::async` 来计算两个数之和: ```cpp #include <iostream> #include <future> int add(int a, int b) { return a + b; } int main() { // 使用 std::launch::async 策略 std::future<int> result = std::async(std::launch::async, add, 5, 7); // 获取异步任务的结果 std::cout << "Result: " << result.get() << std::endl; return 0; } ``` 在这个例子中,`add` 函数被作为异步任务提交给 `std::async` 执行,并且其结果可以通过 `std::future` 对象获得。 --- #### 默认行为 vs 显式策略 如果显式提供启动策略,则默认采用 `std::launch::async | std::launch::deferred` 组合模式。这表示标准库会选择一种合适的策略来优化性能。如果希望强制某种特定行为,可以在调用时传递相应的枚举值。 例如,仅允许延迟执行: ```cpp std::future<int> deferred_result = std::async(std::launch::deferred, add, 3, 4); ``` 或者只允许并发执行: ```cpp std::future<int> async_result = std::async(std::launch::async, add, 3, 4); ``` --- #### 结果同步机制 当需要等待异步任务完成时,可以调用 `std::future::get()` 方法。此方法会阻塞当前线程直到任务结束为止。另外还有非阻塞方式检查任务的状态,比如利用 `wait_for` 和 `wait_until` 成员函数。 示例演示超时检测功能: ```cpp if (result.wait_for(std::chrono::seconds(2)) == std::future_status::ready) { std::cout << "Task completed within timeout." << std::endl; } else { std::cout << "Timeout occurred before task finished!" << std::endl; } ``` 上述片段展示了设置最大等待时间为两秒钟的情况下的处理逻辑。 --- #### 配置注意事项 为了确保正确使用 `std::async`,需要注意以下几点事项: 1. 如果多个地方频繁依赖于同一份数据资源,在设计阶段就要考虑加锁保护共享变量; 2. 要忘记释放使用的 future 实例以防内存泄漏; 3. 编译器支持情况可能有所同,请确认目标平台已启用相应特性开关(如 `-std=c++11`)[^2]。 --- #### 日志集成实例 结合前面提到的日志框架 spdlog ,我们可以构建更复杂的场景应用案例。假设我们需要记录大量调试信息而影响主流程效率,那么就可以借助异步工厂简化开发过程[^3]: ```cpp #include "spdlog/spdlog.h" #include "spdlog/async.h" void setup_async_logger() { try { spdlog::init_thread_pool(8192, 1); // 设置缓冲区大小以及工作线程数量 auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>(); // 注册新的异步日志器 auto logger = spdlog::create_async<std::mutex>("my_async_logger", console_sink); logger->info("This is an asynchronous log message."); } catch(const spdlog::spdlog_ex& ex){ std::cerr << "Log initialization failed: " << ex.what() << '\n'; } } int main(){ setup_async_logger(); return 0; } ``` 这里我们定义了一个名为 `setup_async_logger` 的辅助函数负责初始化环境,并启用了多线程安全版本的标准输出颜色化终端接收端口。最终实现了高效稳定的后台打印效果。 ---
评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值