併發運行時是 C++ 併發編程框架。併發運行時可簡化並行編程,並幫助您編寫可靠、可伸縮且具有響應能力的並行應用程序。併發運行時提高了抽象級別,因此您不必管理與併發相關的基礎結構詳細信息。利用併發運行時,還可以指定滿足應用程序服務質量要求的計劃策略。
併發運行時爲同時運行的應用程序和應用程序組件提供了一致性和可預見性。協作任務計劃和協作阻止是併發運行時優點的兩個示例。
併發運行時使用協作任務計劃程序實現工作竊取算法以在計算資源之間有效地分配工作。例如,假設應用程序具有兩個線程,這兩個線程均由同一運行時進行管理。如果一個線程完成了其計劃任務,則它可以卸載其他線程的工作。此機制可平衡應用程序的總體工作負荷。
併發運行時還提供了使用協作阻止同步訪問資源的同步基元。例如,假設任務必須以獨佔方式訪問共享資源。通過以協作方式進行阻止,當第一個任務等待資源時,運行時可以使用剩餘的量程執行另一個任務。此機制將提升計算資源的最大使用率。
體系結構
併發運行時分爲以下四個組件:並行模式庫 (PPL)、異步代理庫、任務計劃程序和資源管理器。這些組件駐留在操作系統和應用程序之間。下圖演示了併發運行時組件在操作系統和應用程序中的交互方式:
併發運行時具有很好的可組合性,也就是說您可以組合現有功能來執行更多操作。併發運行時利用較低級別的組件來提供多種功能,如並行算法。
1)並行模式庫
並行模式庫 (PPL) 爲執行細粒度並行操作提供通用的容器和算法。PPL 通過提供並行算法來啓用命令性數據並行,這些並行算法將在計算資源之間分配數據集或數據集合計算工作。它還通過提供任務對象來啓用任務並行,這些任務對象將在計算資源之間分配多個獨立的操作。
當具有可以從並行執行中受益的本地計算時,請使用並行模式庫。例如,您可以使用parallel_for算法轉換現有 for 循環,從而以並行方式進行操作。
2)異步代理庫
異步代理庫(或只稱作代理庫)爲粗粒度數據流和管道任務提供了基於參與者的編程模型和消息傳遞接口。異步代理通過在其他組件等待數據時執行工作,使您能夠卓有成效地利用延遲。
當您具有彼此異步通信的多個實體時,請使用代理庫。例如,您可以創建一個代理以從文件或網絡連接中讀取數據,然後使用消息傳遞接口將該數據發送到另一個代理。
3)任務計劃程序
任務計劃程序可在運行時計劃和協調任務。任務計劃程序具有協作功能,它使用工作竊取算法實現處理資源的最大使用率。
併發運行時提供了默認計劃程序,因此您無需管理基礎結構詳細信息。但是,爲了滿足應用程序的質量要求,您也可以提供自己的計劃策略或將特定計劃程序與特定任務相關聯。
4)資源管理器
資源管理器的作用是管理諸如處理器和內存之類的計算資源。如果工作負荷在運行時發生更改,資源管理器會將資源分配到效用最高的地方來響應工作負荷。
資源管理器將充當抽象的計算資源,主要與任務計劃程序進行交互。雖然可以使用資源管理器精細調整庫和應用程序的性能,但通常應使用並行模式庫、代理庫和任務計劃程序提供的功能。當工作負荷發生變化時,這些庫將使用資源管理器動態地重新平衡資源。
示例 - 使用PPL並行計算斐波納契數列
#include <vector>
#include <xutility>
#include <iostream>
#include <Windows.h>
#include <ppl.h>
#include <concurrent_vector.h>
#include <array>
#include <tuple>
#include <algorithm>
// 封裝調用Function並計算耗時
template <class Function>
__int64 time_call(Function&& f) // && - 右值引用聲明
{
__int64 begin = GetTickCount();
f();
return GetTickCount() - begin;
}
// 計算斐波納契數列
int fibonacci(int n)
{
if(n < 2) return n;
return fibonacci(n-1) + fibonacci(n-2);
}
int _tmain(int argc, _TCHAR* argv[])
{
__int64 elapsed;
// 要計算的第n個斐波納契數列的數組
std::tr1::array<int, 4> a = { 41, 24, 26, 42 };
// 順序執行的結果數組
std::vector< std::tr1::tuple<int,int> > results1;
// 併發執行的結果數組
Concurrency::concurrent_vector<std::tr1::tuple<int,int>> results2;
// 使用for_each + lamada表括達式順序計算斐波納契數列並將結果存儲起來
elapsed = time_call([&]
{
std::for_each (a.begin(), a.end(), [&](int n) {
results1.push_back(std::tr1::make_tuple(n, fibonacci(n)));
});
});
std::wcout << L"serial time: " << elapsed << L" ms" << std::endl;
// 打印結果
std::for_each (results1.begin(), results1.end(), [](std::tr1::tuple<int,int>& opair) {
std::wcout << L"fib(" << std::tr1::get<0>(opair) << L"): " << std::tr1::get<1>(opair) << std::endl;
});
std::wcout << std::endl;
// 使用parallel_for_each併發執行同樣的任務
elapsed = time_call([&]
{
Concurrency::parallel_for_each (a.begin(), a.end(), [&](int n) {
results2.push_back(std::tr1::make_tuple(n, fibonacci(n)));
});
});
std::wcout << L"parallel time: " << elapsed << L" ms" << std::endl;
// 打印結果
std::for_each (results2.begin(), results2.end(), [](std::tr1::tuple<int,int>& opair) {
std::wcout << L"fib(" << std::tr1::get<0>(opair) << L"): " << std::tr1::get<1>(opair) << std::endl;
});
return 0;
}
在四核處理器的計算機上,執行結果如下:
serial time: 35069 ms
fib(41): 165580141
fib(24): 46368
fib(26): 121393
fib(42): 267914296
parallel time: 21684 ms
fib(24): 46368
fib(26): 121393
fib(41): 165580141
fib(42): 267914296
由於Concurrency::parallel_for_each併發執行,其運算結果的順序是不固定的。