並行模式庫PPL應用實戰(一):使用task類創建並行任務

自 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 中的說明:

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)
推薦閱讀:

軟體項目開發,全系列規範及約束文件
輕鬆理解UML用例圖時序圖類圖的教程
Windows 10原生應用將迎來獨立Insider預覽項目

TAG:C | 並行編程 | 軟體開發 |