你好,我是雨乐~
今天,我们聊聊C++中一个非常重要的特性,即如何提升程序性能。
一般提到这个问题,第一时间想到的就是并发,即将尽可能多的任务放到其他线程去做,类似于我们常说的MapReduce
,即映射和归纳,本文的主题也类似,不过是通过各种不同的方式来实现。
同步
同步,一般指的是宏观上的单步,即运行过程(假如去掉指令重排以及CPU的乱序执行外)和结果和我们的预期一致。
先看看下面这个例子:
int main() {
auto &&sum = [](auto &&a, auto &&b) {
return a + b;
};
auto &&result = sum(1, 2);
// do sth
}
上面的例子很简单,定义一个lambda,其作用是求和,然后下一步对两个整数1和2
进行求和操作。
嗯,如果上面的lambda是一个很重的操作,即重CPU或者重IO或者二者兼有之,这种情况下,我们就得等,等待其执行完后,再用其结果进行后续操作。假如所在的服务器是单核的话,上面这种实现完全没问题,但在多核普遍的今天,如果不把处理器的性能榨干,就说明我们的程序性能优化还缺点火候,因此,针对上述例子,准备使用多种方式进行优化。
Thread
确切的说,自C++11起,才有了自己的多线程对象或者接口来支持创建多线程程序(C++11之前,需要使用pthread等方式)。
我们使用STL中的std::thread
来实现此功能。
#include <thread>
int main() {
int32_t result{};
auto &&sum = [&](auto &&a, auto &&b) {
result = a + b;
};
std::thread t(sum, 1, 2);
t.join();
return0;
}
std::async
使用std::async来实现此功能的话,可以像如下这样操作:
int main() {
auto &&sum = [](auto &&a, auto &&b) {
return a + b;
};
auto &&fut = std::async(sum, 1, 2);
auto &&result = fut.get();
// do sth
return0;
}
上述代码中,重点关注两点:
1、std::async
:
• std::async(sum, 1, 2)
会异步执行sum
lambda,将1
和2
作为参数传递给它。• std::async
返回一个std::future
对象fut
,它代表异步任务的结果。通过调用fut.get()
,可以等待异步任务的完成并获取其结果。
2、fut.get()
:
• fut.get()
会阻塞当前线程,直到异步任务执行完毕并返回结果。在此代码中,sum
lambda 会计算1 + 2
并返回3
。• fut.get()
会获取该返回值并将其赋值给result
。
在默认情况下,async是新开一个线程来执行任务或者说是继续在当前线程执行,这个依赖于当时的场景。不过,可以通过指定policy来决定使用哪种:
• std::launch::async:使用新线程执行此任务 • std::launch::deferred:在当前线程执行此任务,功能类似于之前的文章:C++进阶指南:优化程序性能之惰性求值
PS:如果没有显式指定policy,则策略默认是std::launch::async | std::launch::deferred
,即是使用新线程还是当前线程依赖于当时的场景。
package_task
std::packaged_task
是一个包装器,用于封装可调用对象(例如函数、lambda 表达式或函数对象)。通过 std::packaged_task
,我们可以将一个可调用对象和 std::future
结合使用,异步执行该可调用对象并获取结果。
其声明如下:
template< class R, class ...ArgTypes >
class packaged_task<R(ArgTypes...)>;
可以像下面这样使用:
int main() {
std::packaged_task<int(int, int)> sum([](int a, int b){ return a + b;});
auto && r = sum(10, 20);
auto &&result = r.get();
// do sth
return0;
}
上面这个例子在方式上类似于直接执行一个lambda callable对象,当然了,也可以像如下这样异步操作:
int main() {
std::packaged_task<int(int, int)> sum([](int a, int b){ return a + b;});
auto &&future = sum.get_future();
std::thread task_td(std::move(sum), 2, 10);
task_td.join();
auto &&result = future.get();
// do sth
return0;
}
结语
本文从thread、async以及package_task几个角度说明了如何使用异步方式进行求值的方式,本文作为一个综合,没有进行过多的深入分析,后面将针对这几个概念进行详细分析。
下期见~
以上
如果对本文有疑问可以加笔者微信直接交流,笔者也建了C/C++相关的技术群,有兴趣的可以联系笔者加群。
3、呃,竟然死锁了~