C++并发编程框架--并发运行时

并发运行时是 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并发执行,其运算结果的顺序是不固定的。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章