一、异步任务基础

1.1、厘清概念

同步 vs 异步(关注通信机制)

  • 同步:调用者主动等待被调用者返回的结果
  • 异步:调用者不等待结果,被调用者完成后通过回调方式通知调用者
    阻塞 vs 非阻塞(关注等待状态):
  • 阻塞:调用者等待被调用者的结果,直到返回,期间不能做其他事情
  • 非阻塞:调用者不等待被调用者的结果,可以继续做其他事情

1.2、四种组合模式

通过生活中点奶茶的例子,来进行理解

模式 现实场景 技术特点
同步阻塞 去奶茶店点奶茶,拿到号之后,就一直站在柜台前等待奶茶的到来,期间不能做其他事情 线程挂起,主动等待结果
同步非阻塞 去奶茶店点奶茶,拿到号之后,可以做其他事情(刷刷短视频,看看朋友圈等),但是每隔一段时间就去询问奶茶是否做好了 线程运行,主动轮询结果
异步阻塞 去奶茶店点奶茶,拿到号之后,坐在店里的等待区,奶茶做好了之后,店员会叫号通知你来取奶茶
异步非阻塞 去奶茶店点奶茶,拿到号之后,可以去附近逛下,奶茶做好了之后,店员会打电话通知你来取奶茶 线程运行,被动等待通知

1.3、异步任务

异步任务,就是一个任务独立于主线程,主线程无需等待任务完成便可继续执行其他任务。当异步任务完成之后,可通过某种机制来获取异步任务的结果。-------异步非阻塞

之前在TrinityCore的异步连接池中,就大量的使用异步任务
c++中,是通过std::asyncstd::future来实现的。

二、async与future

2.1、std::async 启动策略

std::async是C++11中引入的一个函数,用于启动一个异步任务。

std::future<T> std::async(std::launch policy, F&& func, Args&&... args);

启动策略:

- std::launch::async:强制在新线程中异步执行
- std::launch::deferred:延迟执行,直到调用get()或wait()时才在当前线程执行
注意: 如果不显式指定策略,系统会根据实现选择默认策略,可能导致不同平台行为不一致。

2.2、std::future 结果获取

std::future是C++11中引入的一个类,主要用于获取异步任务的结果。

template<class T>
class std::future 
{
public:
    void wait() const;    // 阻塞直到任务完成
    T get();              // 获取结果(可能阻塞)
    // ... 其他成员函数
};

关键方法

  • wait()
    • 阻塞等待异步任务的结束
    • 不关系异步任务的结果,只关心是否完成;换句话说就是用于没有返回值的异步任务,不会抛出异常
  • get()
    • 存在两个返回值,一个是异常,另一个是异步任务的结果
    • 一般需要使用try catch来捕获异常

2.3、示例代码

#include <iostream>
#include <future>
#include <thread>
#include <chrono>

// 有返回值的异步任务
int multiplyTask(const int& num) 
{
    std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时操作
    return num * 3;
}

// 无返回值的异步任务
void printTask() {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Print task completed!" << std::endl;
}

int main() 
{
    int input = 20;
    
    // 示例1:有返回值的异步任务
    {
        std::future<int> result = std::async(std::launch::async, multiplyTask, input);

        try {
            int value = result.get();  // 阻塞直到结果可用
            std::cout << "Result: " << value << std::endl;
        } catch (const std::exception& e) {
            std::cout << "Exception caught: " << e.what() << std::endl;
        }
    }

    // 示例2:无返回值的异步任务
    {
        std::future<void> result = std::async(std::launch::async, printTask);
        result.wait();  // 只等待完成,不获取返回值
        std::cout << "Print task finished waiting." << std::endl;
    }
    
    return 0;
}

2.4、非阻塞获得异步任务结果

#include <iostream>
#include <future>
#include <chrono>

int longRunningTask() 
{
    std::this_thread::sleep_for(std::chrono::seconds(3));
    return 42;
}

int main() 
{
    auto future = std::async(std::launch::async, longRunningTask);
    
    // 使用 wait_for 进行超时检查
    while (true) {
        auto status = future.wait_for(std::chrono::milliseconds(500));
        
        if (status == std::future_status::ready) {
            std::cout << "Task completed! Result: " << future.get() << std::endl;
            break;
        } else if (status == std::future_status::timeout) {
            std::cout << "Task still running, doing other work..." << std::endl;
            // 执行其他工作
        } else if (status == std::future_status::deferred) {
            std::cout << "Task is deferred" << std::endl;
            break;
        }
    }
    
    return 0;
}
  • wait_for(duration):等待指定时长,返回任务状态
  • wait_until(time_point):等待到指定时间点,返回任务状态
    返回值说明:
    • ready:任务已完成,结果可用
    • timeout:超时,任务仍在运行
    • deferred:任务被延迟(仅使用deferred策略时)

2.5、延迟任务

// 延迟执行示例
auto deferredFuture = std::async(std::launch::deferred, []() {
    std::cout << "This task runs only when get() or wait() is called" << std::endl;
    return 100;
});

// 此时任务还未执行
std::cout << "Doing some work..." << std::endl;

// 现在才真正执行任务
int result = deferredFuture.get();  // 任务在此处执行

适用场景:

  • 系统负载较高时推迟非紧急任务
  • 结果不立即需要但最终需要获取的任务
  • 避免不必要的线程创建开销

2.6、共享future

#include <iostream>
#include <future>
#include <vector>
#include <thread>

int computeValue() 
{
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return 42;
}

int main() {
    // 创建异步任务
    std::future<int> fut = std::async(std::launch::async, computeValue);
    
    // 转换为 shared_future 供多个消费者使用
    std::shared_future<int> shared_fut = fut.share();
    
    // 多个线程可以安全地访问结果
    std::vector<std::thread> threads;
    for (int i = 0; i < 3; ++i) {
        threads.emplace_back([i, shared_fut]() {
            int result = shared_fut.get();  // 所有线程获取相同结果
            std::cout << "Thread " << i << " got: " << result << std::endl;
        });
    }
    
    for (auto& t : threads) {
        t.join();
    }
    
    return 0;
}

future:适合一次性获取异步执行结果,多次调用get会抛出异常。

shared_future 特点:

  • 允许多次调用 get() 方法
  • 多个消费者可以共享同一个异步结果
  • 线程安全,适合多线程环境

三、总结

  1. 明确指定启动策略,避免平台差异性

  2. 及时处理异常,使用 try-catch 包装 get() 调用

  3. 合理使用等待机制,根据场景选择阻塞或非阻塞方式

  4. 多消费者场景使用 shared_future,避免多次创建相同任务

  5. 注意生命周期管理,确保 future 对象在有效期内使用

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐