实战总结-C++并发应用
[TOC]
基于前几天学习的C++并发实战《C++ Concurrency in Action》进行自己的实战.
场景为拟合B样条曲线后等长分段过程中的分点求一阶导数的计算.结果显示较大计算量时并发节省时间效果明显.
代码模型搭建
拟合B样条曲线后需要对其进行等长分段,用弦长近似求弧长中需要进行分点求一阶导数,点数按照需求精度决定,精度高时需要的点数很多,如果能多线程求解能取得精度与效率的平衡.参照《C++ Concurrency in Action》的第三章内容得到如下执行模型.
踩过的坑
Thread构造函数
初始化构造函数声明如下:
1 | template <class Fn, class... Args> |
因为我是在类里初始化thread,需要调用类内的成员函数,所以参数一直匹配不上,找到如下参考,解决.
1 | Define INVOKE (f, t1, t2, ..., tN) as follows: |
注意对于f函数中引用形式的参数需要用std::ref()强制转换成引用.因为为了保证参数能outlive thread的生命周期,所以一般都是把参数copy到thread实例里,如果要保持引用的话需要强制引用.
同时如果有些值只对某个thread必要,也可以用std::move传入,不用copy.
一样的错误,我尝试把sharedData Interface类放入std::unique_ptr中然后传入f函数中,报错error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr.unique_ptr(const unique_ptr&) = delete;
因为多个threads都会去调用各自的f函数,因此需要把参数copy多份分给各个thread,但是std::unique_ptr是没有复制操作符的.
把std::unique_ptr改为std::shared_ptr解决.
同理地,对于std::async
跟std::bind
的构造函数也需要注意到上面.
mutex的误嵌套
真的是纸上的来终觉浅啊,书中强调过如果要使用嵌套锁,需使用std::recursive_mutex,我根本没这个意识,所以子函数里mutex一下,母函数里有mutex了,导致一直阻塞,程序停在那里.
1 | void FuncA(){ |
涉及到sharedData接口类里的嵌套成员函数时注意使用std::recursive_mutex.
join之前判断joinable
把分段的数据中的一段交给main thread执行后报错:terminate called after throwing an instance of 'std::system_error' what(): Invalid argument
.
原来是有线程已经join了,后面还是不加判断地全部join导致的问题.
1 | for (auto &thread : threads){ |
sharedData数据结构
因为要求的数据需要考虑顺序,所以先想到的是2维数组,基于2维vector实现,每个thread的输出分别为一个1维vector,为2维vector的元素.最后再首位相接拼成一个1维vector.
跑通后省略2维vector,直接用迭代器访问修改1维vector.
GDB调试
下面是常用的几个多线程调试命令:
1 | info threads #线程信息汇总 |
tip:多线程下不支持倒退调试.
结果
改变计算点数目,10万点时,多开2个线程平均速度提升近60%,快与线程数的增加成正比了.
1万点时,多开2个线程平均速度提升近20%,并不稳定偶尔多线程变现还不如单线程.
1千点时,多开2个线程平均速度提升近10%,表现也不稳定,偶尔多线程变现还不如单线程.
因此单次计算量太小时,也许单线程就够了.但是当计算量很大的时候,多线程能显著降低运行时间,这对RTOS下程序很重要.
ps.计算平台Intel Core i7-6600U CPU @ 2.60GHz × 4
2