• 并行模式库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 中对 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)

    作者:小时了了
    原创文章,欢迎转载,但请以链接形式注明本文地址.
  • 相关阅读:
    人工智能-球星产生式系统实验报告
    [SCOI2009]windy数
    [Atcoder Code Festival 2017 Qual A Problem D]Four Coloring
    [AtCoder Grand Contest 024 Problem E]Sequence Growing Hard
    [AtCoder Grand Contest 025 Problem D]Choosing Points
    [Atcoder Code Festival 2017 Qual B Problem F]Largest Smallest Cyclic Shift
    [CQOI2018]九连环
    [CTSC2017]吉夫特
    [HAOI2006]均分数据
    [JSOI2004]平衡点
  • 原文地址:https://www.cnblogs.com/xrunning/p/7039490.html
Copyright © 2020-2023  润新知