关于虚幻多线程的学习

 
 

1 先去复习了下C++的多线程异步和单线程异步

关于创建虚幻中的三种多线程实现:

. 继承FRunnable接口创建单个线程。

. 直接创建AsyncTask来调用线程池里空闲的线程。

. 用过TaskGraph系统来异步完成一些自定义任务。

2 看了下虚幻中,用Tick模拟局部异步,算是单线程异步

3 根据官方文档,继承FRunnable类来进行虚幻中的多线程使用

    在涉及到使用共享资源及线程同步的时候,用到了FScopeLock锁,FScopeLock(&CriticalSection).    里面的是
  FCriticalSection * 并且最后要在主线程使用时,可以创建多个线程,然后 创建FRunnableThread 使用Create函数运行

  关于Runnable的方式,观看大佬Jerish的文章后,进行补充:
  从下往上,依次是,真正的线程是FRunnableThread是个抽象类,本身没有具体实现关于真正线程,而              FRunnableThreadWin是windows平台中生成该平台线程的一个线程类,其中真正调用了创建线程的API接口。
  而我们在虚幻中使用的FRunnable是继承自FRunnableThread,仅仅只是具体实现Run方法,而不是真正的创建线程。(解释起来就是,我们在使用时及继承FRunnable,然后实现Run方法来把我们的Run任务分发给其他线程去执行。)

4 使用AsyncTask的多线程(AsyncWork.h中有基础例子)

  4.1 AsyncTask系统是一套基于线程池的异步任务处理系统。其中拥有ue的FQueuedThreadPool线程池是它的特点。使得使用者不需要自己再手动创建新的线程。
  4.2 其中有两种AsyncTask:FAutoDeleteAsyncTask与FAsyncTask。FAutoDeleteAsyncTask在任务完成后会通过线程池的Destroy函数删除自身或者在执行DoWork后删除自身,而FAsyncTask需要手动delete。
  4.3 引用大佬的类图:
  

   4.4 关于AsyncTask的FScopeLock和FNonAbandonableTask以及AsyncTask与转发构造

  4.4.1 FScopeLock是UE提供的一种基于作用域的锁,思想类似RAII机制。在构造时对当前区域加锁,离开作用域时执行析构并解锁。UE里面有很多带有“Scope”关键字的类,如移动组件中的FScopedMovementUpdate,Task系统中的FScopeCycleCounter,FScopedEvent等,他们的实现思路是类似的。

  4.4.2 FNonAbandonableTask

  继承FNonAbandonableTask的Task不可以在执行阶段终止,即使执行Abandon函数也会去触发DoWork函数。

  

   4.4.3 创建自定义任务的方式如下

  FAsyncTask<ExampleAsyncTask>*MyTask= new FAsyncTask<ExampleAsyncTask>(5);

  括号里面的5会以参数转发的方式传到的ExampleAsyncTask构造函数里面,这一步涉及到C++11的右值引用与转发构造。

 

5 TaskGraph系统的多线程

  Task Graph 系统是UE4一套抽象的异步任务处理系统,可以创建多个多线程任务,指定各个任务之间的依赖关系,按照该关系来依次处理任务。

  TaskGraph分为两种任务,有命名任务和无具体命名任务,其中有命名任务有5种Stat/Render/Game/RHI/Audio/ActualRendering。有命名的任务一旦放入执行队列中就不能随意调整任务了并且是由FThreadTaskQueue来安排处理执行顺序;无具体命名任务也就是自定义任务,是可以随意调整任务的顺序队列的。

  TaskGraph系统中任务和任务之间是有依赖关系的,彼此可能在不同的线程中,由不同的触发事件的完成来进行任务的执行。如图:

   其中在创建任务时,就会选择好任务的触发事件

  FGraphEventRef Join=TGraphTask<FVictoryTestTask>::CreateTask(NULL, ENamedThreads::GameThread).ConstructAndDispatchWhenReady();
  CreateTask的第一个参数就是该任务依赖事件数组(这里为NULL),如果传入一个事件数组的话,那么当前任务就会通过SetupPrereqs函数设置这些依赖事件,并且在所有依赖事件都触发后再将该任务放到任务队列里面分配给线程执行。
  当执行CreateTask时,会通过FGraphEvent::CreateGraphEvent()构建一个新的后续事件,再通过函数ConstructAndDispatchWhenReady返回。这样我们就可以在当前的位置执行:
  FTaskGraphInterface::Get().WaitUntilTaskCompletes(Join, ENamedThreads::GameThread_Local);
  让当前线程等待该任务结束并触发事件后再继续执行,当前面这个事件完成后,就会调用DispatchSubsequents()去触发他后续的任务。WaitUntilTaskCompletes函数的第二个参数必须是当前的线程类型而且是带名字的。
  

  不要在非GameThread线程内执行下面几个操作:

  • 不要 Spawn / Modify/ delete UObjects or AActors
  • 不要使用定时器 TimerManager
  • 不要使用任何绘制接口,例如 DrawDebugLine

最后附上观看的大佬的地址:https://zhuanlan.zhihu.com/p/38881269

 
posted @ 2023-05-25 21:00  蜡笔小新紫南  阅读(153)  评论(0编辑  收藏  举报