C++11之std::async使用介绍

在C++11中有个async异步函数,其声明如下:

template <class Fn, class... Args>
future<typename result_of<Fn(Args...)>::type>
  async (launch policy, Fn&& fn, Args&&... args);

该模板函数 async 异步地运行函数 fn 并返回最终将保存该函数调用结果的 std::future 中,其中policy策略有以下三种:

// 异步启动的策略  
enum class launch {  
    // 异步启动,在调用std::async()时创建一个新的线程以异步调用函数,并返回future对象;
    async = 0x1,
    // 延迟启动,在调用std::async()时不创建线程,直到调用了future对象的get()或wait()方法时,才创建线程;                    
    deferred = 0x2,  
     // 自动,函数在某一时刻自动选择策略,这取决于系统和库的实现,通常是优化系统中当前并发的可用性           
    any = async | deferred,      
    sync = deferred  
}; 
  • 参数 fn 是要调用的可调用 (Callable) 对象
  • 参数args 是传递给 f 的参数

异步调用函数可以大大提高程序运行的效率,验证如下:

辅助函数和声明

这个是验证程序的通用代码:

#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <chrono>
#include <mutex>
#include <memory>
#include <functional>
#include <future>

using namespace std;

typedef std::chrono::steady_clock                STEADY_CLOCK;
typedef std::chrono::steady_clock::time_point    TIME_POINT;

void print_diff_time(const TIME_POINT& t1, const TIME_POINT& t2)
{
    std::chrono::duration<double, std::milli> dTimeSpan = std::chrono::duration<double,std::milli>(t2-t1);

    std::cout << "time span : " << dTimeSpan.count() << "ms\n";  
}

原始程序

下面演示的是,最原始的程序计算代码,关键代码如下:

//计算A函数
int calculateA()
{
    //延迟1ms
    this_thread::sleep_for(std::chrono::milliseconds(1));
    return 1;
}

//计算B函数
int calculateB()
{
    //延迟2ms
    this_thread::sleep_for(std::chrono::milliseconds(2));
    return 2;
}

//计算C函数
int calculateC()
{
    //延迟3ms
    this_thread::sleep_for(std::chrono::milliseconds(3));
    return 3;
}

//统计三者的和
void test_common_time()
{
    TIME_POINT t1 = STEADY_CLOCK::now();

    int c = calculateA() + (calculateB() + calculateC());

    TIME_POINT t2 = STEADY_CLOCK::now();

    cout << "test_common_time result is :" <<  c << "  ";
    print_diff_time(t1, t2);
}

运行结果:

test_common_time result is :6  time span : 6.55506ms

多线程计算

没有引入多线程计算,程序计算时间达6.5ms,相对效率不高,下面演示多线程计算,代码如下:

//防止数据竞争
std::mutex g_mtu;

void calA(int& sum)
{
    this_thread::sleep_for(std::chrono::milliseconds(1));
    //多线程加锁保护
    lock_guard<mutex> lk(g_mtu);
    sum += 1;
}

void calB(int& sum)
{
    this_thread::sleep_for(std::chrono::milliseconds(2));
    lock_guard<mutex> lk(g_mtu);
    sum += 2;
}

void calC(int& sum)
{
    this_thread::sleep_for(std::chrono::milliseconds(3));
    lock_guard<mutex> lk(g_mtu);
    sum += 3;
}

//case2
void test_threads_time()
{
    int sum = 0;

    TIME_POINT t11 = STEADY_CLOCK::now();
    //启动三个线程,进行运算
    thread t1(calA, ref(sum));
    thread t2(calB, ref(sum)); 
    thread t3(calC, ref(sum)); 
    //等待计算完成
    t1.join();
    t2.join();
    t3.join();
    //计算完成
    TIME_POINT t22 = STEADY_CLOCK::now();
    cout << "test_threads_time result is :" <<  sum << "  ";
    print_diff_time(t11, t22);

}

运行结果:

test_threads_time result is :6  time span : 3.57699ms

程序计算时间将近减少了一半,效率提高了很多,但是程序引入了锁机制,程序的实现相对复杂了。

异步调用

在C++11中引入了async异步调用函数,其封装了异步(多线程)实现的复杂过程,将计算结果保存在future<>中,通过get()获取每个对象的最终结果。代码如下:

//case3
void test_feture_time()
{
    int sum = 0;

    TIME_POINT t11 = STEADY_CLOCK::now();

    //异步调用
    future<int> f1 = std::async(calculateA);
    future<int> f2 = std::async(calculateB);
    future<int> f3 = std::async(calculateC);
    //get()函数会阻塞,直到结果返回
    sum = f1.get() + f2.get() + f3.get();

    TIME_POINT t22 = STEADY_CLOCK::now();

    cout << "test_feture_time result is :" <<  sum << "  ";

    print_diff_time(t11, t22);       
}

运行结果:

test_future_time result is :6  time span : 3.76372ms

相比多线程版本的计算过程,async异步调用整个程序更加简洁,没有明显引入线程,锁,缓冲区等概念。

总结

async异步调用函数通过启动一个新线程或者复用一个它认为合适的已有线程,async是语义上的并发即可,并关心具体的调用策略。

”简单性”是async/future设计中最重视的一个方面;future一般也可以和线程一起使用,不过不要使用async()来启动类似I/O操作,操作互斥体(mutex),多任务交互操作等复杂任务。async()背后的理念和range-for statement很类似:简单事儿简单做,把复杂的事情留给一般的通用机制来搞定。

猜你喜欢

转载自blog.csdn.net/xiao3404/article/details/79506347