UE4 多线程 创建与使用

UE4 多线程使用分为以下几种:

1.创建标准线程

2.使用线程池

3.使用TaskGraph系统

 

使用线程时需要注意,不要在非GameThread线程内执行下面几个操作:

 

  • 不要 创建/ 修改/ 删除 任何 派生自UObject的对象。

  • 不要使用定时器 TimerManager

  • 不要使用任何绘制接口,例如 DrawDebugLine

 

 

1.创建标准线程

创建标准线程的使用方法有点类似java中创建线程的方法,主要有以下两个类:

 

FRunnable:线程执行类的基类

FRunnableThread:线程类,根据平台不同会通过静态方法Create创建不同的线程类,比如FRunnableThreadWin, FRunnableThreadAndroid等等

 

使用方法:

先派生一个FRunnable的执行类,并创建类对象,

然后使用FRunnableThread::Create创建线程并将对象传入。

通常做法:创建FRunnable派生类对象并创建线程的过程放在FRunnable派生类的构造函数中。

 

FRunnable派生类必须重写基类的Run方法。下面是FRunnable类的格式

 

class CORE_API FRunnable
{

public:
//初始化FRunable对象,该方法在新线程(执行Run的线程)中被执行,而不是在创建FRunable的线程中执行,返回true 表示初始化成功,将执行FRunable的Run方法,否则FRunnableThread中的线程将立即结束,Run不会执行
virtual bool Init()。
{
    return true;
}


//该方法只有在Init()返回true 时,才执行,是线程中具体执行的任务。
virtual uint32 Run() = 0;


//用于提供提前停止线程的执行的条件。通常会设置一个停止标志,然后在Run中检测这个标志,检测后停止线程。经常与FRunnableThread的WaitForCompletion方法一起使用。WaitForCompletion方法会阻塞直到线程退出。
//Stop方法在FRunnableThread的kill方法中被调用(kill可以选择等待还是不等待线程结束,kill实际也是通过stop来停止线程,如果stop没有执行停止线程的操作,则skill如果是等待线程,则将一致阻塞),而kill方法也会被FRunnableThread::~FRunnableThread()自动调用,这时kill是等待线程结束的
virtual void Stop() { }


//该方法在Run结束后执行,该方法只有在Init()返回true 时,才执行,该方法在执行Run的线程中被执行,而不是在创建FRunable的线程中执行
virtual void Exit() { }

...
};

具体用法如下:

class UZMQServerThread : public FRunnable
{
public:

       UZMQServerThread();
       ~UZMQServerThread();

       virtual bool Init() override;
       virtual uint32 Run() override;
       virtual void Stop()override;
       virtual void Exit() override;
private:
        //这里用FThreadSafeCounter应该更好
       bool bStop;
       FRunnableThread* Thread;
};

UZMQServerThread::UZMQServerThread() {
        //这里创建线程对象并传入FRunnable对象和线程的名称,还可以设置线程优先级,默认是TPri_Normal。
       Thread = FRunnableThread::Create(this, TEXT("ZMQBodyThread"));
}

void UZMQServerThread::Stop() {
       bStop = true;
}

void UZMQServerThread::Exit() {
}

UZMQServerThread::~UZMQServerThread()
{
       if (Thread) {
               //这里会自动调用FRunnableThread的kill方法,FRunnableThread的kill会调用UZMQServerThread::Stop,并阻塞直到线程停止。
              delete Thread;
              Thread = nullptr;
       }
}

bool UZMQServerThread::Init()
{
       bool ret = true;
        //这里是初始化代码,返回值决定是否执行Run的
       return ret;
}


uint32 UZMQServerThread::Run()
{
       while (!bStop)
       {
            //这里是你要执行的代码
       }
       return 0;
}

 

2.使用线程池中的线程。

使用线程池通常是通过FAsyncTaskFAutoDeleteAsyncTask来使用的,这两个类都是所谓的异步任务。 异步任务可以用于执行一直运行的异步处理(比如while(1)),也可以用于执行时间较长的异步操作。

使用FAsyncTask需要手动删除,FAutoDeleteAsyncTask会在任务完成时自动删除。

 

使用异步任务,需要先定义一个异步任务的执行类型,执行类型应该派生自FNonAbandonableTask,具体形式如下

 

class ExampleAsyncTask : public FNonAbandonableTask
{
       friend class FAsyncTask<ExampleAsyncTask>;
       
       int32 ExampleData;

       ExampleAsyncTask(int32 InExampleData)

              : ExampleData(InExampleData)
       {

       }

       void DoWork()
       {
              ... do the work here
       }

       FORCEINLINE TStatId GetStatId() const
       {

              RETURN_QUICK_DECLARE_CYCLE_STAT(ExampleAsyncTask, STATGROUP_ThreadPoolAsyncTasks);
       }
};

 

具体使用异步任务的方法为:

 

void Example()
{
       //start an example job

       FAsyncTask<ExampleAsyncTask>* MyTask = new FAsyncTask<ExampleAsyncTask>(5)//这里的5是传给ExampleAsyncTask构造函数的参数,他的构造函数在当前线程执行;

       MyTask->StartBackgroundTask();//执行异步任务

       //--or --

       MyTask->StartSynchronousTask();//执行同步任务,既任务在该条语句堵塞执行,直到完成

       //to just do it now on this thread

       //Check if the task is done :

       if (MyTask->IsDone())//判断是否完成,不会堵塞

       {

       }

       //Spinning on IsDone is not acceptable( see EnsureCompletion ), but it is ok to check once a frame.

       //Ensure the task is done, doing the task on the current thread if it has not been started, waiting until completion in all cases.

       MyTask->EnsureCompletion(); //该函数检测完成,如果未完成,将等待直到完成,将导致当前线程堵塞

       delete Task;

}

 

 

3.使用TaskGraph系统

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

在引擎初始化FTaskGraphImplementation的时候,我们就会默认构建24个FWorkerThread工作线程(这里支持最大的线程数量也就是24),其中里面有5个是默认带名字的线程,StatThread、RHIThread、AudioThread(声音线程)、GameThread(主线程,游戏线程)、ActualRenderingThread(渲染线程),还有前面提到的N个非指定名称的任意线程,这个N由CPU核数决定。对于带有名字的线程,他不需要创建新的Runnable线程,因为他们会在其他的时机创建,如StatThread以及RenderingThread会在FEngineLoop.PreInit里创建。

 

taskGraph系统适用于那些比较小的异步处理,大型的异步处理任务,应该使用创建标准线程FRunnable和使用FAsyncTask(尤其不能在GameTread里放置耗时的任务,甚至是while(1),这将严重影响帧率)。

使用TaskGraph,首先要创建TaskGraph所需的任务类型,任务类型创建如下(有点类似FAsyncTask的实际执行类型):

 

 

class FGenericTask
{
       TSomeType     SomeArgument;

public:
       FGenericTask(TSomeType InSomeArgument) // CAUTION!: Must not use references  in the constructor args; use pointers instead if you need by reference

              : SomeArgument(InSomeArgument)

       {
              // Usually the constructor doesn't do anything except save the  arguments for use in DoWork or GetDesiredThread.
       }

       ~FGenericTask()
       {
              // you will be destroyed immediately after you execute. Might as  well do cleanup in DoWork, but you could also use a destructor.

       }

       FORCEINLINE TStatId GetStatId() const
       {
              RETURN_QUICK_DECLARE_CYCLE_STAT(FGenericTask, STATGROUP_TaskGraphTasks);
       }

       static ENamedThreads::Type GetDesiredThread()//返回所需要运行在的线程
       {
              return ENamedThreads::[named thread or AnyThread];
       }

       void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef&  MyCompletionGraphEvent)//实际执行的任务

       {
              // The arguments are useful for setting up other tasks.
              // Do work here, probably using SomeArgument.
              MyCompletionGraphEvent->DontCompleteUntil(TGraphTask<FSomeChildTask>::CreateTask(NULL, CurrentThread).ConstructAndDispatchWhenReady());

       }
};

 

具体使用如下:

FGraphEventRef Join = TGraphTask<FGenericTask>::CreateTask().ConstructAndDispatchWhenReady(SomeArgument);

//下面的语句是用来检测任务是否完成,不堵塞

if(Join->IsComplete()){....}

//下面的语句将阻塞当前线程等待任务完成

FTaskGraphInterface::Get().WaitUntilTaskCompletes(Join);

4.使用快捷创建函数使用以上三种线程方式

快捷创建函数有两个,一个是Async,一个是AsyncTask。

Async函数: 多线程任务执行。

TFuture<ResultTypeAsync(EAsyncExecution Execution, TFunction<ResultType()> Function, TFunction<void()> CompletionCallback = TFunction<void()>())

enum class EAsyncExecution
{
       /** Execute in Task Graph (for short running tasks). */
       TaskGraph,//这个是使用taskGraph,线程类型为ENamedThreads::AnyThread
       
       /** Execute in separate thread (for long running tasks). */
       Thread,//这个是使用做标准的FRunnableThread和FRunnable
       
       /** Execute in global queued thread pool. */
       ThreadPool//这个是使用线程池GThreadPool,类似于AsyncTask

};

TFuture,其中ResultType则是传入的执行函数的返回值。

TFunction<int()> My_Task= []() {
    return 123;
};

auto Future = Async(EAsyncExecution::TaskGraph, My_Task);

//任务返程后通过下面获得结果

int Result = Future.Get();

 

AsyncTask函数

其实是使用了taskGraph,可以自己指定线程类型

void AsyncTask(ENamedThreads::Type Thread, TFunction<void()> Function)

{

       TGraphTask<FAsyncGraphTask>::CreateTask().ConstructAndDispatchWhenReady(Thread, MoveTemp(Function));

}

### 回答1: 在UE4中,可以通过使用多线程来运行插件,以提高程序的性能和并行处理能力。 首先,插件在UE4中是以模块的形式存在的,可以独立于游戏项目而存在。为了实现多线程运行插件,可以在插件的模块中编写异步任务,利用多线程来执行这些任务。 在UE4中,我们可以使用FRunnable接口来创建自定义线程。首先,需要创建一个继承自FRunnable的类,并实现必要的接口函数,如Run和Stop。在Run函数中,编写插件需要执行的任务代码。 在插件的初始化阶段,可以通过调用FRunnableThread::Create函数来创建一个新的线程,并指定我们刚刚定义的FRunnable类对象作为参数。然后,可以开始执行插件任务。 需要注意的是,在多线程编程中,需要合理地处理线程之间的数据共享和同步问题。可以使用一些线程同步的机制,如互斥锁、信号量等,来避免多个线程同时操作共享数据导致的冲突。 另外,UE4还提供了一些已经封装好的多线程工具类,如FGraphEvent和FQueuedThread等,可以帮助我们更方便地实现多线程任务的管理和同步。 总结来说,UE4中可以通过使用FRunnable接口和多线程工具类来实现插件的多线程运行。合理地设计和管理多线程任务,可以提高程序的性能和并行处理能力,并且确保线程之间的数据共享和同步的正确性。 ### 回答2: 在UE4中,我们可以通过多线程来实现插件的运行。多线程是一种并发执行任务的方式,可以让程序同时执行多个任务,提高了程序的效率和性能。 在UE4中,我们可以使用一些多线程的技术来实现插件的运行。比如,使用C++中的std::thread来创建多个线程,并让每个线程在后台执行插件相关的任务。同时,我们还可以使用一些线程同步的机制,比如互斥锁、条件变量等来管理多个线程之间的资源访问和共享。 在插件的运行过程中,可以将一些耗时的任务放在单独的线程中执行,这样就不会阻塞主线程的执行。比如,可以将插件中的某个算法或者处理逻辑放在一个独立的线程中运行,这样主线程可以继续执行其他的任务,提高了程序的响应速度和用户体验。 除了使用多线程,还可以使用UE4中提供的任务图谱系统来实现插件的并行执行。任务图谱系统可以将任务划分成多个小任务,并按照依赖关系进行调度,从而充分利用计算资源并提高任务执行的效率。 总结来说,UE4中可以通过多线程技术来实现插件的运行,可以提高程序的效率和性能,同时还可以使用任务图谱系统来实现任务的并行执行。这些技术可以充分发挥计算资源的利用率,提高插件的运行效果和用户体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值