向左右向右走 —— 小时了了的技术博客

关注C++开发技术、架构设计、软件项目管理、软件产品管理等

posts - 24,comments - 59,views - 36166

自 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)

posted on   小时了了  阅读(1931)  评论(0编辑  收藏  举报
编辑推荐:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· 【全网最全教程】使用最强DeepSeekR1+联网的火山引擎,没有生成长度限制,DeepSeek本体
< 2025年2月 >
26 27 28 29 30 31 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 1
2 3 4 5 6 7 8

点击右上角即可分享
微信分享提示