异步编程中std::async的使用

std::async 是一个 C++ 标准库中的函数模板,用于异步执行可调用对象(例如函数、lambda 表达式或函数对象)。它提供了一种简便的方式来启动异步操作,并通过 std::future 对象获取异步操作的结果。下面我将从简单到复杂,一步一步详细解释 std::async 的用法和相关概念。


1. 基本概念

std::async 的主要作用是异步执行一个任务,并返回一个 std::future 对象,通过这个对象可以获取任务的执行结果或等待任务完成。它的基本语法如下:

template <class Fn, class... Args>
std::future<typename std::result_of<Fn(Args...)>::type> async(std::launch policy, Fn&& fn, Args&&... args);
  • Fn:可调用对象(函数、lambda、成员函数等)。
  • Args:传递给可调用对象的参数。
  • policy:启动策略,决定任务是在新线程中异步执行还是延迟执行。

2. 启动策略

std::async 支持两种主要的启动策略:

  • std::launch::async:任务在新线程中异步执行。
  • std::launch::deferred:任务延迟执行,直到 std::futureget()wait() 被调用时才在当前线程中执行。

如果不指定启动策略,默认行为是 std::launch::async | std::launch::deferred,即实现可以选择异步或延迟执行。

示例:异步执行
#include 
#include 

int main() {
    // 使用 std::launch::async 在新线程中异步执行
    auto future = std::async(std::launch::async, []() {
        std::cout << "异步任务执行中..." << std::endl;
        return 42;
    });
    
    // 获取结果
    int result = future.get();
    std::cout << "结果: " << result << std::endl;
}

输出

异步任务执行中...
结果: 42

3. 获取异步任务的结果

std::async 返回一个 std::future 对象,可以通过 future.get() 获取异步任务的返回值。如果任务尚未完成,get() 会阻塞当前线程直到任务完成。

注意事项:
  • get() 只能调用一次,重复调用会导致未定义行为。
  • 如果异步任务抛出异常,get() 会重新抛出该异常。
示例:异常处理
#include 
#include 

int main() {
    auto future = std::async(std::launch::async, []() {
        throw std::runtime_error("错误");
        return 42;
    });

    try {
        int result = future.get();  // 会抛出异常
    } catch (const std::exception& e) {
        std::cout << "捕获异常: " << e.what() << std::endl;
    }
}

输出

捕获异常: 错误

4. 延迟执行

当使用 std::launch::deferred 时,任务不会立即执行,而是延迟到 future.get()future.wait() 被调用时才在当前线程中执行。

示例:延迟执行
#include 
#include 

int main() {
    auto future = std::async(std::launch::deferred, []() {
        std::cout << "延迟任务执行中..." << std::endl;
        return 42;
    });

    // 在这里,任务尚未执行
    std::cout << "主线程继续执行..." << std::endl;

    // 调用 get() 时,任务在当前线程中执行
    int result = future.get();
    std::cout << "结果: " << result << std::endl;
}

输出

主线程继续执行...
延迟任务执行中...
结果: 42

5. 使用成员函数

std::async 也可以用于异步调用类的成员函数。需要传递对象的指针或引用,并指定成员函数。

示例:调用成员函数
#include 
#include 

class MyClass {
public:
    int add(int a, int b) {
        return a + b;
    }
};

int main() {
    MyClass obj;
    auto future = std::async(&MyClass::add, &obj, 3, 4);
    int result = future.get();  // result == 7
    std::cout << "结果: " << result << std::endl;
}

输出

结果: 7

6. 管理多个异步任务

可以同时启动多个异步任务,并通过 std::future 管理它们。可以使用 std::future::wait_forstd::future::wait_until 来等待任务完成。

示例:多个任务
#include 
#include 

int main() {
    auto future1 = std::async(std::launch::async, []() {
        std::cout << "任务1执行中..." << std::endl;
        return 10;
    });
    auto future2 = std::async(std::launch::async, []() {
        std::cout << "任务2执行中..." << std::endl;
        return 20;
    });

    // 等待两个任务完成
    int result1 = future1.get();
    int result2 = future2.get();
    std::cout << "总和: " << (result1 + result2) << std::endl;
}

输出(顺序可能不同):

任务1执行中...
任务2执行中...
总和: 30

7. 异常处理

如果异步任务抛出异常,std::future::get() 会重新抛出该异常。因此,建议在 get() 调用时使用 try-catch 块来处理可能抛出的异常。


8. 注意事项和最佳实践

  • 对象生命周期:确保传递给 std::async 的对象(尤其是通过引用传递的)在异步任务执行期间保持有效,避免悬垂引用。
  • 线程管理:使用 std::launch::async 时会创建新线程,过多线程可能导致性能下降,因此需要合理管理线程数量。
  • 任务粒度:异步任务应该足够大,以抵消线程创建和管理的开销,小任务可能不适合异步执行。
  • 避免阻塞:在异步任务中避免长时间阻塞操作,以免影响性能。

9. 高级示例:并行计算

假设我们需要并行计算多个任务的和:

#include 
#include 
#include 

int main() {
    std::vector<std::future<int>> futures;
    for (int i = 0; i < 5; ++i) {
        futures.push_back(std::async(std::launch::async, [i]() {
            return i * i;
        }));
    }
    
    int sum = 0;
    for (auto& fut : futures) {
        sum += fut.get();
    }
    std::cout << "总和: " << sum << std::endl;  // 0+1+4+9+16=30
}

输出

总和: 30

10. std::async 和 std::thread的区别

std::asyncstd::thread 都是 C++ 标准库中用于并发编程的工具,但它们在设计目的、使用方式和功能特性上有显著的区别。理解这些区别有助于选择适合特定场景的工具。以下是 std::threadstd::async 的详细对比。


1. 基本概念
  • std::thread
    是一个类,代表一个单独的执行线程。创建 std::thread 对象时,会启动一个新线程,并在该线程中执行提供的函数。你需要手动管理线程的生命周期,包括加入(join)或分离(detach)线程。

  • std::async
    是一个函数模板,用于异步执行一个函数,并返回一个 std::future 对象。通过这个 std::future 对象,你可以获取异步操作的结果或等待操作完成。std::async 抽象了线程管理的细节,使得异步编程更加简便。


2. 主要区别
2.1 抽象级别
  • std::thread
    提供对线程的低级控制,允许你直接管理线程的创建、执行和销毁。适合需要细粒度控制的场景。
  • std::async
    提供高级抽象,专注于函数的异步执行,内部管理线程或任务的调度。适合大多数异步任务,特别是需要返回结果的场景。
2.2 获取结果
  • std::thread
    没有内置机制来获取线程执行的结果。你需要使用共享变量、条件变量或其他同步机制来传递结果。
  • std::async
    返回一个 std::future 对象,可以通过 future.get() 直接获取异步操作的结果,简化了数据传递。
2.3 异常处理
  • std::thread
    如果线程函数抛出异常,必须在该线程内捕获和处理,否则程序将终止。
  • std::async
    如果异步函数抛出异常,异常会被捕获并存储在 std::future 中。当调用 future.get() 时,异常会被重新抛出,允许在调用线程中处理。
2.4 执行控制
  • std::thread
    创建时立即启动一个新线程执行函数。
  • std::async
    允许指定执行策略:
    • std::launch::async:在单独的线程中异步执行。
    • std::launch::deferred:延迟执行,直到调用 future.get()future.wait() 时才在当前线程中执行。
    • 如果不指定策略,实现会选择异步或延迟执行。
2.5 资源管理
  • std::thread
    每次创建 std::thread 对象通常会启动一个新线程,频繁创建线程可能导致资源消耗较大。
  • std::async
    std::launch::async 策略下,可能会重用线程池中的线程,从而提高资源利用效率(具体取决于实现)。
2.6 生命周期管理
  • std::thread
    必须在 std::thread 对象销毁前显式调用 join()detach(),否则程序将终止。
  • std::async
    std::future 对象管理异步操作。如果 future 对象在未调用 get()wait() 的情况下被销毁,并且操作是以 std::launch::async 启动的,析构函数可能会阻塞直到操作完成(具体行为取决于实现)。
2.7 使用场景
  • std::thread
    适用于需要直接控制线程的场景,如实现线程池或需要线程独立运行而不返回结果的任务。
  • std::async
    适用于需要异步执行任务并获取结果的场景,特别是当异常处理和执行策略灵活性很重要时。

3. 示例对比
示例 1:基本用法
  • 使用 std::thread

    #include 
    #include 
    
    void print_hello() {
        std::cout << "Hello from thread" << std::endl;
    }
    
    int main() {
        std::thread t(print_hello);
        t.join();
        return 0;
    }
    
  • 使用 std::async

    #include 
    #include 
    
    int main() {
        auto fut = std::async(std::launch::async, []() {
            std::cout << "Hello from async" << std::endl;
        });
        fut.get();  // 等待完成
        return 0;
    }
    

    说明:两者都能实现异步执行,但 std::async 通过 std::future 提供了更简洁的等待机制。

示例 2:获取结果
  • 使用 std::thread

    #include 
    #include 
    #include 
    
    std::mutex mtx;
    int result;
    
    void compute_result() {
        std::lock_guard<std::mutex> lock(mtx);
        result = 42;
    }
    
    int main() {
        std::thread t(compute_result);
        t.join();
        std::cout << "Result: " << result << std::endl;
        return 0;
    }
    
  • 使用 std::async

    #include 
    #include 
    
    int compute_result() {
        return 42;
    }
    
    int main() {
        auto fut = std::async(std::launch::async, compute_result);
        std::cout << "Result: " << fut.get() << std::endl;
        return 0;
    }
    

    说明std::async 通过 std::future 直接返回结果,代码更简洁且无需手动同步。

示例 3:异常处理
  • 使用 std::thread

    #include 
    #include 
    
    void throw_exception() {
        try {
            throw std::runtime_error("Error in thread");
        } catch (const std::exception& e) {
            std::cout << "Caught in thread: " << e.what() << std::endl;
        }
    }
    
    int main() {
        std::thread t(throw_exception);
        t.join();
        return 0;
    }
    
  • 使用 std::async

    #include 
    #include 
    
    int throw_exception() {
        throw std::runtime_error("Error in async");
        return 0;
    }
    
    int main() {
        try {
            auto fut = std::async(std::launch::async, throw_exception);
            fut.get();
        } catch (const std::exception& e) {
            std::cout << "Caught exception: " << e.what() << std::endl;
        }
        return 0;
    }
    

    说明std::async 允许在主线程中捕获和处理异步操作中的异常,而 std::thread 要求在线程内部处理。

示例 4:延迟执行
  • 使用 std::async(延迟执行)
    #include 
    #include 
    
    int compute_result() {
        std::cout << "Computing result" << std::endl;
        return 42;
    }
    
    int main() {
        auto fut = std::async(std::launch::deferred, compute_result);
        std::cout << "Main thread continues" << std::endl;
        int result = fut.get();  // 在此处执行 compute_result
        std::cout << "Result: " << result << std::endl;
        return 0;
    }
    
    输出
    Main thread continues
    Computing result
    Result: 42
    
    说明std::thread 没有直接支持延迟执行的功能。

4. 总结

std::threadstd::async 都是 C++ 中用于并发编程的重要工具,但它们在设计和使用上有明显的区别:

  • std::thread

    • 提供对线程的低级控制,适合需要直接管理线程的场景。
    • 需要手动处理结果传递和异常。
    • 线程立即启动,无法延迟执行。
  • std::async

    • 提供高级抽象,简化异步编程,自动管理线程或任务。
    • 通过 std::future 轻松获取结果并处理异常。
    • 支持异步和延迟执行策略,灵活性更高。
何时使用
  • 使用 std::thread

    • 需要细粒度控制线程时。
    • 线程需要独立运行且不返回结果时。
    • 实现线程池或其他自定义线程管理时。
  • 使用 std::async

    • 需要异步执行任务并获取结果时。
    • 异常处理很重要时。
    • 需要延迟执行的灵活性时。
    • 希望代码简洁且易于维护时。

你可能感兴趣的:(C/C++,c++)