concurrency runtime学习笔记之二:并行
并行依赖的是底层多线程处理机制,线程的创建和销毁,还有线程间的同步问题常常令人望而生畏。concrrency runtime提供的并行库Parallell Patterns Library (PPL)提高了线程处理机制的抽象级别,让C++多线程并行变得异常的简单。
先看一下构架:
应该很清楚了,PPL处于应用程序和线程处理机制之间。
再来看一个常规的例子,计算斐波那契数列:
#include <windows.h> #include <ppl.h> #include <array> using namespace std; using namespace Concurrency; // Computes the nth Fibonacci number. int fibonacci(int n) { if(n < 2) return n; return fibonacci(n-1) + fibonacci(n-2); } // Calls the provided work function and returns the number of milliseconds // that it takes to call that function. template <class Function> __int64 time_call(Function&& f) { __int64 begin = GetTickCount(); f(); return GetTickCount() - begin; } int _tmain(int argc, _TCHAR* argv[]) { __int64 elapsed; // An array of Fibonacci numbers to compute. array<int, 4> a = { 24, 26, 41, 42 }; // Use the for_each algorithm to compute the results serially. elapsed = time_call([&] { size_t sum = 0; for_each (a.begin(), a.end(), [&](int n) { sum += fibonacci(n); }); wcout << sum << endl; }); wcout << L"serial time: " << elapsed << L" ms" << endl; return 0; }
如果调用并行算法,就把for_each换成parallel_for_each,不过要注意在一个并行循环体内,对共享资源的操作只允许读不允许写,所以要实现累加的话需要用到互斥对象 Concurrency::critical_section :
elapsed = time_call([&] { critical_section cs; size_t sum = 0; parallel_for_each (a.begin(), a.end(), [&](int n) { cs.lock(); sum += fibonacci(n); cs.unlock(); }); wcout << sum << endl; }); wcout << L"parallel time: " << elapsed << L" ms" << endl;
更好的办法是用Concurrency::combinable 来协调线程间的冲突:
elapsed = time_call([&] { combinable<size_t> sum; parallel_for_each (a.begin(), a.end(), [&](int n) { sum.local() += fibonacci(n); }); size_t fibsum = sum.combine(std::plus<int>()); wcout << fibsum << endl; }); wcout << L"parallel time: " << elapsed << L" ms" << endl;
取消并行循环要将并行算法包含在一个任务组中,并用Concurrency::task_group::cancel或Concurrency::structured_task_group::cancel取消这个任务组,因为从构架上来说,并行库是在task scheduler模块之下的,所以取消机制是自上而下的,也就是说取消的是整个任务组,看程序:
structured_task_group tasks; tasks.run_and_wait([&] { parallel_for_each(a.begin(), a.end(), [&](int n)
{ if (n == 41) { tasks.cancel(); } }); });
以上是parallel_for_each算法的大致应用,parallel_for算法用法大同小异,parallel_invoke用的较少。尽管concurrency runtime封装并优化了多线程调用,但是线程调用的开销仍是存在的,运用时要考虑下是否值得。一般来说,计算密集型的应用比较合适,比方说图象处理算法,而对于小型轻量循环体就弊大于利了。
--------------------------------------------------------------------
附一道面试题:一个人走楼梯,可以一次走1层,也可以走2层,也可以走3层,问n层楼梯有多少种走法。
解:
1----> 1
1阶楼梯的爬法总共为: 1
2----> 1 1
2----> 2
2阶楼梯的爬法总共为: 2
3----> 1 1 1
3----> 1 2
3----> 2 1
3----> 3
3阶楼梯的爬法总共为: 4
4----> 1 1 1 1
4----> 1 1 2
4----> 1 2 1
4----> 1 3
4----> 2 1 1
4----> 2 2
4----> 3 1
4阶楼梯的爬法总共为: 7
5----> 1 1 1 1 1
5----> 1 1 1 2
5----> 1 1 2 1
5----> 1 1 3
5----> 1 2 1 1
5----> 1 2 2
5----> 1 3 1
5----> 2 1 1 1
5----> 2 1 2
5----> 2 2 1
5----> 2 3
5----> 3 1 1
5----> 3 2
5阶楼梯的爬法总共为: 13
看下规律,大概就是个fibonacci数列,f(n) = f(n-1) + f(n-2) + f(n-3) ,其中f(1)=1,f(2)=2,f(3)=4