自 VS2010 起,微軟就在 CRT 中集成了併發運行時(Concurrency Runtime),並行模式庫(PPL,Parallel Patterns Library)是其中的一個重要組成部分。7 年過去了,似乎大家都不怎麼Care這個事情,相關文章少少且多是蜻蜓點水。實際上這個庫的設計相當精 ...
自 VS2010 起,微軟就在 CRT 中集成了併發運行時(Concurrency Runtime),並行模式庫(PPL,Parallel Patterns Library)是其中的一個重要組成部分。7 年過去了,似乎大家都不怎麼Care這個事情,相關文章少少且多是蜻蜓點水。實際上這個庫的設計相當精彩,勝過 C++ 標準庫中 future/promise/async 系列許多,所以計劃寫一個系列探討 PPL 在實際項目中應用中的各種細節。
好了,從最簡單的代碼開始,先演示下如何使用 task 類和 lambda 表達式創建一個並行任務:
// final_answer.cpp // compile with: /EHsc #include <ppltasks.h> #include <iostream> using namespace concurrency; using namespace std; int main(int argc, char *argv[]) { task<int> final_answer([] { return 42; }); cout << "The final answer is: " << final_answer.get() << endl; return 0; }
使用 Visual Studio 命令行工具編譯
cl /EHsc final_answer.cpp
執行結果為:
The final answer is: 42
task 類的原型如下:
template<typename _ReturnType>
class task;
其模板參數 _ReturnType 是任務返回值類型。 task:get 方法則用於獲取返回值,原型如下:
_ReturnType get() const;
task 類的構造函數原型:
template<typename T> __declspec(noinline) explicit task(T _Param);
可以看到這是個模板函數,其參數 _Param 可以是 lambda 表達式、函數對象、仿函數、函數指針等可以以 _Param()
形式調用的類型,或者 PPL 中的 task_completion_event<result_type> 類型。因此可以使用各種靈活的方式構造 task 對象,其中 lambda 表達式無疑是最方便常用的一種。
接下來我們修改上面的程式,列印出線程 id 以便觀察並行任務的執行情況。
// final_answer_1.cpp // compile with: /EHsc #include <ppltasks.h> #include <iostream> #include <thread> using namespace concurrency; using namespace std; int main(int argc, char *argv[]) { cout << "Major thread id is: " << this_thread::get_id() << endl; task<int> final_answer([] { cout << "Thread id in task is:" << this_thread::get_id() << endl; return 42; }); cout << "The final answer is: " << final_answer.get() << endl; return 0; }
繼續編譯執行,得到輸出結果:
Major thread id is: 164824
Thread id in task is: 164824
The final answer is: 42
註意兩個線程 id 是相同的,很有些意外,任務是在主線程執行的而非預計的其他後臺工作線程。實際上這是 PPL 的優化策略造成的。
再修改下程式,在 task 對象構造完成後加一個 sleep 調用掛起當前線程一小段時間:
int main(int argc, char *argv[]) { cout << "Major thread id is: " << this_thread::get_id() << endl; task<int> final_answer([] { cout << "Thread id in task is:" << this_thread::get_id() << endl; return 42; }); this_thread::sleep_for(chrono::milliseconds(1)); cout << "The final answer is: " << final_answer.get() << endl; return 0; }
這次輸出結果發生了變化:
Major thread id is: 173404
Thread id in task is: 185936
The final answer is: 42
PPL 使用了一個新的線程執行並行任務,實際上 PPL 是使用了線程池來執行被調度到的任務。
而在上一個程式中,由於沒有 sleep,也沒有其他耗時的代碼,執行到 task::get 方法時並行任務尚未被調度所以直接在當前線程執行該任務,這樣就節省了兩次線程切換的開銷。
MSDN 中對 task::wait 方法的說明:
It is possible for wait to execute the task inline, if all of the tasks dependencies are satisfied, and it has not already been picked up for execution by a background worker.
task::get 方法的內部實現會先調用 task::wait 方法所以有同樣的效果。
本章小結:
1. task 類對象構造完成後即可被調度執行;
2. 並行有可能被優化在當前線程執行;
留一個問題,如果 task 對象構造後馬上析構,該並行任務是否會被調度執行呢?
本章代碼使用 visual studio community 2013 編譯調試通過。
本章參考文檔:
How to: Create a Task that Completes After a Delay
task Class (Concurrency Runtime)