两个计算函数:
#include <iostream>
#include <future> //async, future
#include "MyCPUTimer.hpp"
using namespace std;
int calc_1()
{
long r = 0;
for(int t = 1; t <= 10; ++t)
{
for(int i = -999990; i <= 999990; ++ i)
r += i;
r /= t;
}
return r;
}
int calc_2()
{
long r = 0;
for(int t = 1; t <= 10; ++t)
{
for(int i = -999990; i <= 999990; ++i)
r -= i;
r /= t;
}
return r;
}
我们做第一个测试,串行调用二者,并输出用时,计时器就是用第10章《STL和boost》“日期和时间”小节中,我们自行实现的计时器。当然,也可以引入boost相关的库,直接使用该库的cpu_timer工具类
void test1()
{
MyCPUTimer timer(true);
int sum = calc_1() + calc_2();
timer.Stop();
cout << sum << ", use " << timer.Elapsed() << endl;
}
先执行calc_1(),再执行calc_2(),两只串行执行,用时一共108ms
如果并行执行呢?
C++11提供了两个关键组件用于支持如下功能:
一是当前线程不等某件事做完(甚至不等它是否开始),继续往下做事:标准库的asyn()函数模板
二是当前线程在后续某个时候,等待前面事做完并得到结果:标准库的future数据类模板。
async是“asynchronous(异步的)”的意思,简化后的“声明”如下:
future <T> async(Function&& f, args&&... args);
第一个入参f是指C++中的可执行体(比如函数指针、函数对象、lambda表达式、std::function对象等),后续可变个数的入参,是执行f时所需要的入参。
以calc_1()函数为例,由于该函数不需要入参,所以想要异步调用它,只需写“async(calc_1)”即可。
future是“未来”或“期货”的意思,它正是async()函数返回结果的数据类型。更好的理解是将它当做“存根”或“收条”理解,我们让async()去做某件事,但它不能立即返回做此事的结果,只是给我们一张“收条”,未来我们可以依据它来取得真正的结果。
以调用calc_1()这件事为例,该函数真正返回的结果是一个整数,所以需要为future类模板指定int作为模板入参:
future <int> future_1 = async(calc_1);
这行代码的解读是:我们真正想做的是调用calc_1()并得到它的结果,但我们不想等它执行,所以将它交给async()异步处理,async收到任务后给了我们一张收据,名为future_1,并且根据这张收条,将来可以得到int类型的数据(即calc_1()的执行结果)。从收条得到真正的结果,代码也很简单:
int result_1 = future_1.get();
就是在future对象上调用get()方法,此时如果async已经调用完calc_1(),就直接返回后者执行结果,如果还没有,就立即调用以便返回结果。
根据以上内容,写了test2();
void test2()
{
MyCPUTimer timer(true);
std::future <int> future_1 = std::async(calc_1);
std::future <int> future_2 = std::async(calc_2);
int sum = future_1.get() + future_2.get();
timer.Stop();
cout << sum << ", use " << timer.Elapsed() << endl;
}
可以看到,用时53毫秒,执行时间缩短了一半时间。
int main()
{
test1();
test2();
return 0;
}
我们让两个此时,放到一个main中调用,反复执行
会发现,test1()的耗时较为稳定。相比之下,test2()耗时变化幅度较大,原因在于async比较任性,当我们执行语句“async(calc_1);”时,它会立即启动一个新的线程以便执行calc_1,但也可能因为外部资源紧张等原因而拖拖拉拉甚至无动于衷。因此代码应该尽量给各异步操作多一些时间,在逻辑允许的情况下,尽量迟一些调用future对象的get()成员。
我们让主线程,也参与到计算中去:
void test3()
{
MyCPUTimer timer(true);
//先交待下属干活
std::future <int> future_1 = std::async(calc_1);
//交待完,领导(调用者线程)别闲着,也干点活
int r2 = calc_2();
int sum = future_1.get() + r2;
timer.Stop();
cout << sum << ", use " << timer.Elapsed() << endl;
}
int main()
{
test1();
test2();
test3();
return 0;
}
运行结果如下:
作业:使用std::thread实现
将上述例子,使用std::thread实现,并思考它和使用“async()(异步调用)”实现有何异同?
#include <iostream>
#include "MyCPUTimer.hpp"
#include <thread>
using namespace std;
int num1, num2;
int calc_1()
{
long r = 0;
for(int t = 1; t <= 10; ++t)
{
for(int i = -999990; i <= 999990; ++i)
r += i;
r /= t;
}
num1 = r;
return r;
}
int calc_2()
{
long r = 0;
for(int t = 1; t <= 10; ++ t)
{
for(int i = -999990; i <= 999990; ++i)
r += i;
r /= t;
}
num2 = r;
return r;
}
int main()
{
MyCPUTimer timer(true);
thread trad_1(&calc_1);
thread trad_2(&calc_2);
trad_1.join();
trad_2.join();
int sum = num1 + num2;
timer.Stop();
cout << sum << ", use " << timer.Elapsed() << endl;
return 0;
}