**标题:** C++17 中的异步编程:std::async 与 std::future 的高级用法

内容:

在 C++17 标准中,异步编程得到了进一步完善。除了 std::asyncstd::future 的基本用法之外,还有许多细节值得深入探讨,例如任务异常传递、协程与异步的结合、以及与线程池的自定义实现。本文将通过一个完整的示例,展示如何构建一个可复用的异步任务框架,支持多种执行策略、错误处理以及结果组合。

  1. 基本概念回顾

    • std::async:启动一个异步任务,返回 std::future
    • std::future:提供 get()wait() 接口,用于同步获取结果。
    • 执行策略:std::launch::async(立即在新线程中执行)与 std::launch::deferred(延迟执行)。
  2. 任务包装器

    template<typename Func, typename... Args>
    auto make_async(Func&& f, Args&&... args) {
        using Ret = std::invoke_result_t<Func, Args...>;
        return std::async(std::launch::async,
                          std::forward <Func>(f),
                          std::forward <Args>(args)...);
    }

    该包装器保证总是以异步方式执行,避免 deferred 的懒执行导致的隐藏线程。

  3. 异常转发
    std::future::get() 会在调用时抛出异步任务中捕获的异常。我们可以在外层捕获并记录,或自定义异常类型。

  4. 结果组合
    当需要等待多个异步结果时,可以使用 std::future_statusstd::wait_for

    template<typename Future1, typename Future2>
    auto join(Future1&& f1, Future2&& f2) {
        struct TupleResult {
            std::decay_t<decltype(f1.get())> first;
            std::decay_t<decltype(f2.get())> second;
        };
        return std::async(std::launch::async, [=] {
            return TupleResult{f1.get(), f2.get()};
        });
    }
  5. 与协程的结合
    C++20 引入了 co_awaitstd::future 的适配器。可以使用 std::experimental::make_ready_future 或自定义 awaiter,实现协程等待异步结果。

  6. 自定义线程池
    std::async 每次调用都会创建新线程,资源消耗高。实现一个简易线程池后,可将异步任务提交到固定线程组。

    class ThreadPool {
        std::vector<std::thread> workers_;
        std::queue<std::function<void()>> tasks_;
        std::mutex mtx_;
        std::condition_variable cv_;
        bool stop_ = false;
    public:
        ThreadPool(size_t n);
        ~ThreadPool();
        template<class F, class... Args>
        auto submit(F&& f, Args&&... args) -> std::future<decltype(f(args...))>;
    };

    submit 中使用 std::packaged_task 包装函数,并将其推入任务队列。

  7. 完整示例

    int heavyComputation(int x) { /* ... */ }
    double readSensor() { /* ... */ }
    
    int main() {
        auto pool = std::make_shared <ThreadPool>(4);
    
        auto fut1 = pool->submit(heavyComputation, 42);
        auto fut2 = pool->submit(readSensor);
    
        try {
            auto [res1, res2] = join(fut1, fut2).get();
            std::cout << "Computation: " << res1 << ", Sensor: " << res2 << '\n';
        } catch (const std::exception& e) {
            std::cerr << "Error: " << e.what() << '\n';
        }
    }
  8. 总结

    • std::asyncstd::future 为 C++ 提供了直观的异步接口。
    • 通过包装器和自定义线程池,可以显著提升性能与可维护性。
    • 结合 C++20 协程,可实现更简洁的异步代码流。

通过上述技术栈,开发者可以在现代 C++ 项目中实现高效、可读、可维护的异步逻辑。

发表评论