当前位置: 首页 > article >正文

【UE5 C++课程系列笔记】26——多线程基础——ParallelFor的简单使用

目录

概念 

函数原型

工作原理

与普通 for 循环对比

注意线程安全 


概念 

  ParallelFor 函数的主要目的是将 for 循环操作并行化,以便充分利用多核处理器的优势,加快处理速度,提升程序的性能。通常用于处理可独立对每个元素进行操作、且相互之间没有严格执行顺序依赖的任务集合,比如对数组中的每个元素进行独立的计算、对一批游戏对象进行相同类型的初始化等操作。

 

函数原型

inline void ParallelFor(int32 Num, TFunctionRef<void(int32)> Body, EParallelForFlags Flags = EParallelForFlags::None)

ParallelFor 函数是一个模板函数,主要接受以下几个参数:

(1)Num 是一个 int32 类型的参数,它指定了循环需要迭代的次数,确定了整个并行循环任务的规模大小,让函数知道需要把任务拆分成多少个子任务来分配到不同线程进行并行处理,是控制循环范围的关键参数

(2)Body 是一个 TFunctionRef<void(int32)> 类型的参数,本质上它是一个可调用对象的引用,通常可以传入 lambda 表达式或者符合相应函数签名的函数指针等。这个可调用对象接受一个 int32 类型的参数,这个参数一般用于表示当前循环迭代的索引值,在可调用对象内部可以根据这个索引值来操作对应的元素或者执行具体的任务逻辑。例如:

ParallelFor(100, [](int32 Index) {
    // 这里可以根据 Index 来操作对应的元素,比如对数组元素进行赋值、计算等
    MyArray[Index] = Index * 2;
});

(3)Flags 是一个 EParallelForFlags 类型的参数,默认值为 EParallelForFlags::None,它用于提供一些额外的执行标志或者控制选项,来影响 ParallelFor 函数的具体执行行为。例如:

        1. EParallelForFlags::BackgroundPriority

        将并行任务的优先级设置为后台优先级。适用于那些不紧急的计算任务,可以允许系统将更多资源分配给前台任务,如渲染或用户交互。

        2. EParallelForFlags::Unbalanced

        指示循环中的任务可能不平衡,即某些任务可能需要比其他任务更多的时间来完成。当任务的执行时间差异较大时使用,可以帮助调度器更好地分配线程资源,提高整体效率。

        3. EParallelForFlags::ForceSingleThread

        强制并行循环在单线程上执行,忽略所有并行设置。用于调试或在某些特定情况下需要确保代码按顺序执行时使用。

        4. EParallelForFlags::PumpRenderingThread

        允许并行循环在执行过程中泵送渲染线程的消息。确保在进行长时间计算时,渲染线程仍然能够处理其消息队列,防止界面卡顿或渲染延迟。

 

工作原理

  ParallelFor 函数内部会根据系统的处理器核心数量以及当前的负载情况等因素,将整个循环任务划分成多个子任务,并将这些子任务分配到不同的线程(通常是线程池中的线程)去并行执行。它会自动处理好线程的调度、任务的分配以及必要的同步等工作,使得开发者可以相对简单地编写并行代码,而无需深入关注底层的多线程管理细节。

        例如,假设有一个四核处理器的系统,使用 ParallelFor 对一个较大的数组进行操作时,它可能会将数组大致平均地分成四个部分(具体的划分策略可能更复杂,会综合考虑多种因素),每个部分的元素操作作为一个子任务,分别交给四个核心对应的线程去同时执行,从而实现并行处理,加快整体的执行速度。

 

与普通 for 循环对比

        普通 for 循环是顺序地依次处理每个元素,只能利用单个核心的计算资源,在面对大量元素操作时,执行速度可能会受限。而 ParallelFor 通过并行化能够利用多核优势,可以同时对多个元素操作,能显著提高处理速度,尤其在处理大规模数据或复杂计算任务时效果更明显。                ​​​​​​​        ParallelFor 虽然能带来性能提升,但由于涉及多线程并行执行,需要考虑更多的因素,比如线程安全问题(在 lambda 表达式中访问共享资源时要特别小心),代码逻辑相对复杂一些,编写和调试时需要更谨慎。

通过如下代码来直观的感受 普通 for 与 ParallelFor 循环的差距:

void UThreadSubsystem::InitParallerFor()
{
    //检查普通for循环的耗时情况
    FDateTime startDateTime1 = FDateTime::UtcNow();
    for (size_t i = 0; i < 10; i++)
    {
        UE_LOG(LogTemp, Warning, TEXT("This is For: %d"), i);
        FPlatformProcess::Sleep(0.1);
    }
    FDateTime endDateTime1 = FDateTime::UtcNow();
    FTimespan elapsedTimeSpan1 = endDateTime1 - startDateTime1;
    double elapsedSeconds1 = elapsedTimeSpan1.GetTotalSeconds();
    UE_LOG(LogTemp, Warning, TEXT("ElapsedSeconds: %f"), elapsedSeconds1);

    //检查普通ParallelFor循环的耗时情况
    FDateTime startDateTime2 = FDateTime::UtcNow();
    ParallelFor(10, [](int32 i) {
        UE_LOG(LogTemp, Warning, TEXT("This is ParallelFor: %d"), i);
        FPlatformProcess::Sleep(0.1);
    }, EParallelForFlags::BackgroundPriority | EParallelForFlags::Unbalanced);
    FDateTime endDateTime2 = FDateTime::UtcNow();
    FTimespan elapsedTimeSpan2 = endDateTime2 - startDateTime2;
    double elapsedSeconds2 = elapsedTimeSpan2.GetTotalSeconds();
    UE_LOG(LogTemp, Warning, TEXT("ElapsedSeconds_ParallelFor: %f"), elapsedSeconds2);
}

执行结果如下,可以看到 ParallelFor 循环的代码执行时间约为 普通 for 的1/10,但是执行顺序是乱的。

 

注意线程安全 

        由于 ParallelFor 是并行执行任务的,当 lambda 表达式中涉及到对共享资源(如全局变量、共享的游戏对象等)的访问时,必须要注意线程安全。如果多个线程同时读写同一个共享资源,没有合适的多线程同步机制保障,很容易导致数据不一致、程序崩溃等问题。

        这里通过对变量加锁来确保同一时刻只有一个线程能够对其进行修改,从而保证线程安全,示例代码如下:

可以看到计算结果相同, ParallelFor 循环的代码执行时间依旧远快于普通 for 循环。


http://www.kler.cn/a/488518.html

相关文章:

  • JVM实战—12.OOM的定位和解决
  • pytest 参数介绍
  • k8s的原理和,k8s的安装
  • Typescript使用指南
  • ubuntu22.04 的录屏软件有哪些?
  • CSS Grid 布局全攻略:从基础到进阶
  • vue的路由守卫逻辑处理不当导致部署在nginx上无法捕捉后端异步响应消息等问题
  • Docker与GitHub的完美结合:6种实用方法
  • 如何搭建appium工具环境?
  • 使用 Multer 上传图片到阿里云 OSS
  • 【NLP 19、词的向量化和文本向量化】
  • 初识MySQL · 数据库
  • 模式识别与机器学习
  • 多类特征(Multiple features)
  • 什么是端口
  • Python 数据建模完整流程指南
  • LeetCode LCP17速算机器人
  • Python标准库之SQLite3
  • 【再谈设计模式】模板方法模式 - 算法骨架的构建者
  • 【游戏设计原理】53 - 解决问题的障碍
  • SOLID原则学习,接口隔离原则
  • AI赋能服装零售:商品计划智能化,化危机为转机
  • SQL Server查询计划操作符——查询计划相关操作符(3)
  • 【HTML+CSS+JS+VUE】web前端教程-16-HTML5新增标签
  • C#Halcon找线封装
  • 关于地平线开发板使用nhwc格式的前向传播输出格式的理解