FRunnable和FRunnableThread方法对于大多数问题来说无疑是一个可行的解决方案。 但是,在创建许多任务时,您可能会达到CPU可以处理的并发上限,此时并发线程实际上会在争用CPU时间时相互阻碍。 然后可能值得查看FQueuedThreadPool以限制任务可用的线程数。
虚幻引擎4还提供了全局GThreadPool ,但是此线程池仅设置为单个线程(UE4.14.3)。 它似乎只是为了在另一个核心上运行并发任务。
下面的代码是什么
- 创建一个线程来计算前50,000个素数
- 将增量完成数据发送回游戏线程
- 具有静态访问器,用于启动,关闭和查找线程是否已完成。
静态函数
您将在下面的代码中注意到我使用静态函数轻松启动新线程,如果我需要匆忙关闭线程(例如播放器退出),我也可以使用GameThread中的静态Shutdown()函数游戏)
性能
我首先使用任务图系统计算前50,000个素数,并创建50,000个任务(每个素数找1个)。
在本教程中看到的代码中,我创建了一个专用线程来计算前50,000个素数!
性能优势是非凡的!
我的fps在下面的代码中为这个线程的整个运行保持稳定90(我选择的最大fps)。
而对于任务图系统,当我接近50,000时,fps下降了最多40。
对于较大的任务,请务必尝试实际的多线程!
.H
//~~~~~ Multi Threading ~~~ class FPrimeNumberWorker : public FRunnable { /** Singleton instance, can access the thread any time via static accessor, if it is active! */ static FPrimeNumberWorker* Runnable; /** Thread to run the worker FRunnable on */ FRunnableThread* Thread; /** The Data Ptr */ TArray<uint32>* PrimeNumbers; /** The PC */ AVictoryGamePlayerController* ThePC; /** Stop this thread? Uses Thread Safe Counter */ FThreadSafeCounter StopTaskCounter; //The actual finding of prime numbers int32 FindNextPrimeNumber(); private: int32 PrimesFoundCount; public: int32 TotalPrimesToFind; //Done? bool IsFinished() const { return PrimesFoundCount >= TotalPrimesToFind; } //~~~ Thread Core Functions ~~~ //Constructor / Destructor FPrimeNumberWorker(TArray<uint32>& TheArray, const int32 IN_PrimesToFindPerTick, AVictoryGamePlayerController* IN_PC); virtual ~FPrimeNumberWorker(); // Begin FRunnable interface. virtual bool Init(); virtual uint32 Run(); virtual void Stop(); // End FRunnable interface /** Makes sure this thread has stopped properly */ void EnsureCompletion(); //~~~ Starting and Stopping Thread ~~~ /* Start the thread and the worker from static (easy access)! This code ensures only 1 Prime Number thread will be able to run at a time. This function returns a handle to the newly started instance. */ static FPrimeNumberWorker* JoyInit(TArray<uint32>& TheArray, const int32 IN_TotalPrimesToFind, AVictoryGamePlayerController* IN_PC); /** Shuts down the thread. Static so it can easily be called from outside the thread context */ static void Shutdown(); static bool IsThreadFinished(); };
.CPP
//*********************************************************** //Thread Worker Starts as NULL, prior to being instanced // This line is essential! Compiler error without it FPrimeNumberWorker* FPrimeNumberWorker::Runnable = NULL; //*********************************************************** FPrimeNumberWorker::FPrimeNumberWorker(TArray<uint32>& TheArray, const int32 IN_TotalPrimesToFind, AVictoryGamePlayerController* IN_PC) : ThePC(IN_PC) , TotalPrimesToFind(IN_TotalPrimesToFind) , StopTaskCounter(0) , PrimesFoundCount(0) { //Link to where data should be stored PrimeNumbers = &TheArray; Thread = FRunnableThread::Create(this, TEXT("FPrimeNumberWorker"), 0, TPri_BelowNormal); //windows default = 8mb for thread, could specify more } FPrimeNumberWorker::~FPrimeNumberWorker() { delete Thread; Thread = NULL; } //Init bool FPrimeNumberWorker::Init() { //Init the Data PrimeNumbers->Empty(); PrimeNumbers->Add(2); PrimeNumbers->Add(3); if(ThePC) { ThePC->ClientMessage("**********************************"); ThePC->ClientMessage("Prime Number Thread Started!"); ThePC->ClientMessage("**********************************"); } return true; } //Run uint32 FPrimeNumberWorker::Run() { //Initial wait before starting FPlatformProcess::Sleep(0.03); //While not told to stop this thread // and not yet finished finding Prime Numbers while (StopTaskCounter.GetValue() == 0 && ! IsFinished()) { PrimeNumbers->Add(FindNextPrimeNumber()); PrimesFoundCount++; //*************************************** //Show Incremental Results in Main Game Thread! // Please note you should not create, destroy, or modify UObjects here. // Do those sort of things after all thread are completed. // All calcs for making stuff can be done in the threads // But the actual making/modifying of the UObjects should be done in main game thread. ThePC->ClientMessage(FString::FromInt(PrimeNumbers->Last())); //*************************************** //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //prevent thread from using too many resources //FPlatformProcess::Sleep(0.01); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ } //Run FPrimeNumberWorker::Shutdown() from the timer in Game Thread that is watching //to see when FPrimeNumberWorker::IsThreadFinished() return 0; } //stop void FPrimeNumberWorker::Stop() { StopTaskCounter.Increment(); } FPrimeNumberWorker* FPrimeNumberWorker::JoyInit(TArray<uint32>& TheArray, const int32 IN_TotalPrimesToFind, AVictoryGamePlayerController* IN_PC) { //Create new instance of thread if it does not exist // and the platform supports multi threading! if (!Runnable && FPlatformProcess::SupportsMultithreading()) { Runnable = new FPrimeNumberWorker(TheArray,IN_TotalPrimesToFind,IN_PC); } return Runnable; } void FPrimeNumberWorker::EnsureCompletion() { Stop(); Thread->WaitForCompletion(); } void FPrimeNumberWorker::Shutdown() { if (Runnable) { Runnable->EnsureCompletion(); delete Runnable; Runnable = NULL; } } bool FPrimeNumberWorker::IsThreadFinished() { if(Runnable) return Runnable->IsFinished(); return true; } int32 FPrimeNumberWorker::FindNextPrimeNumber() { //Last known prime number + 1 int32 TestPrime = PrimeNumbers->Last(); bool NumIsPrime = false; while( ! NumIsPrime) { NumIsPrime = true; //Try Next Number TestPrime++; //Modulus from 2 to current number - 1 for(int32 b = 2; b < TestPrime; b++) { //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //prevent thread from using too many resources //FPlatformProcess::Sleep(0.01); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if(TestPrime % b == 0) { NumIsPrime = false; break; //~~~ } } } //Success! return TestPrime; }
Starting the thread
//In the .h for the player controller // this is the actual data TArray<uint32> PrimeNumbers;
//player controller .cpp //Multi-threading, returns handle that could be cached. // use static function FPrimeNumberWorker::Shutdown() if necessary FPrimeNumberWorker::JoyInit(PrimeNumbers, 50000, this);
线程管理
您应该查看“FRunnable”的代码库,以查看多线程和锁定/解锁保护的扩展用途。
我用了一个简单的例子来帮助你入门,但是多线程时需要考虑很多:)
使用Sleep进行线程管理
你应该考虑使用
FPlatformProcess :: Sleep ( 秒 );
防止1个线程占用太多系统资源:)
什么不该做
- 不要尝试从其他线程修改,创建或删除UObject!
您可以准备所有数据/进行所有计算,但只有游戏线程应该实际产生/修改/删除UObjects / AActors。
- 不要尝试在游戏线程之外使用TimerManager :)
- 不要试图绘制调试行/点等,因为它可能会崩溃,即DrawDebugLine(等...)
-
通知(自4.11起):
如果要使用计时器,删除和修改变量,请使用它:
#include “Async.h” ... AsyncTask ( ENamedThreads :: GameThread , []() { //这里在游戏线程上执行的代码 });
GameThread中的计时器功能
您可以在游戏线程中运行计时器功能,以定期检查您创建的其他线程收集的数据。
如何支持单线程平台?
如果您的代码绝对必须在HTML5等单线程环境中运行,那么请查看
AsyncIOSystemBase.h
struct CORE_API FAsyncIOSystemBase:public FIOSystem,FRunnable,FSingleThreadRunnable
Runnable可以扩展SingleThreadRunnable,并为单线程情况下的FRunnable钩子返回自己:
/** * Gets single thread interface pointer used for ticking this runnable when multi-threading is disabled. * If the interface is not implemented, this runnable will not be ticked when FPlatformProcess::SupportsMultithreading() is false. * * @return Pointer to the single thread interface or nullptr if not implemented. */ virtual class FSingleThreadRunnable* GetSingleThreadInterface( ) { return nullptr; }