ue4-多线程使用
主线程异步或多线程异步的使用在GameThread线程之外的其他线程中,不允许做一下事情不要 spawning / modifying / deleting UObjects / AActors不要使用定时器 TimerManagerDon’t try to draw debug lines/points etc, as it will likely crash, ie DrawDebug
·
主线程异步或多线程异步的使用
在GameThread线程之外的其他线程中,不允许做一下事情
- 不要 spawning / modifying / deleting UObjects / AActors
- 不要使用定时器 TimerManager
-
不要使用任何绘制接口,例如 DrawDebugLine
-
如果想在主线程中异步处理(也就是分帧处理),可以使用一下接口(在 Async.h 中)
AsyncTask(ENamedThreads::GameThread, [&]() { UE_LOG(LogMyTest, Warning, TEXT("--- UMyGameInstance::MyAsyncTask")); SpawnActor(3); });
多线程使用
新开线程线程的类
-
PrimeNumberWorker.h
class UMyGameInstance; //~~~~~ Multi Threading ~~~ class FPrimeNumberWorker : public FRunnable { static FPrimeNumberWorker* Runnable; FRunnableThread* Thread; TArray<uint32>* PrimeNumbers; UMyGameInstance* mGameIns; FThreadSafeCounter StopTaskCounter; //用于多线程间的判断交互,volatile int32 Counter; int32 FindNextPrimeNumber(); FCriticalSection QueueCritical; //互斥锁 FEvent* ThreadSuspendedEvent; //线程悬挂和唤醒事件 public: bool IsFinished(); void Suspend(); void Resume(); //Constructor / Destructor FPrimeNumberWorker(TArray<uint32>& TheArray, UMyGameInstance* GameIns); 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(); FCriticalSection* GetCriticalSection(); static FPrimeNumberWorker* JoyInit(TArray<uint32>& TheArray, UMyGameInstance* GameIns); static FPrimeNumberWorker* Get(); static void Shutdown(); static bool IsThreadFinished(); };
-
PrimeNumberWorker.cpp
#include "MyTest.h" #include "PrimeNumberWorker.h" #include "../MyGameInstance.h" FPrimeNumberWorker* FPrimeNumberWorker::Runnable = nullptr; FPrimeNumberWorker::FPrimeNumberWorker(TArray<uint32>& TheArray, UMyGameInstance* GameIns) : mGameIns(GameIns) , StopTaskCounter(0) { ThreadSuspendedEvent = FPlatformProcess::GetSynchEventFromPool(); PrimeNumbers = &TheArray; Thread = FRunnableThread::Create(this, TEXT("FPrimeNumberWorker"), 0, TPri_BelowNormal); //windows default = 8mb for thread, could specify more } FPrimeNumberWorker::~FPrimeNumberWorker() { delete Thread; Thread = nullptr; FPlatformProcess::ReturnSynchEventToPool(ThreadSuspendedEvent); ThreadSuspendedEvent = nullptr; } bool FPrimeNumberWorker::Init() { PrimeNumbers->Empty(); return true; } uint32 FPrimeNumberWorker::Run() { //Initial wait before starting FPlatformProcess::Sleep(0.03); while (StopTaskCounter.GetValue() == 0 && !IsFinished()) { FScopeLock* QueueLock = new FScopeLock(&QueueCritical); //锁住 //*************************************** //不要 spawning / modifying / deleting UObjects / AActors 等等之类的事 //这里做多线程间共享信息的 modify,如:PrimeNumbers->Add //*************************************** PrimeNumbers->Add(FindNextPrimeNumber()); UE_LOG(LogMyTest, Warning, TEXT("--- FPrimeNumberWorker::Run, lock")); //Suspend(); //prevent thread from using too many resources FPlatformProcess::Sleep(1.0f); //这里睡眠3秒是为了让GameThread中的UMyGameInstance::MyAsyncThread中的日志打不出来 delete QueueLock;//解锁 UE_LOG(LogMyTest, Warning, TEXT("--- FPrimeNumberWorker::Run, unlock")); //FPlatformProcess::Sleep(2.0f); //这里睡眠2秒是为了让GameThread中获取到 互斥锁QueueCritical 并锁住 } Stop(); return 0; } bool FPrimeNumberWorker::IsFinished() { return PrimeNumbers->Num() == 5; } void FPrimeNumberWorker::Suspend() { ThreadSuspendedEvent->Wait(); } void FPrimeNumberWorker::Resume() { ThreadSuspendedEvent->Trigger(); } void FPrimeNumberWorker::Stop() { StopTaskCounter.Increment(); } FPrimeNumberWorker* FPrimeNumberWorker::JoyInit(TArray<uint32>& TheArray, UMyGameInstance* GameIns) { if (!Runnable && FPlatformProcess::SupportsMultithreading()) { Runnable = new FPrimeNumberWorker(TheArray, GameIns); } bool isSupport = FPlatformProcess::SupportsMultithreading(); FString msg = isSupport ? "SupportsMultithread" : "dont SupportsMultithreading"; UE_LOG(LogMyTest, Warning, TEXT("--- FPrimeNumberWorker::JoyInit, msg:%s"), *msg); return Runnable; } FPrimeNumberWorker* FPrimeNumberWorker::Get() { return Runnable; } void FPrimeNumberWorker::EnsureCompletion() { Stop(); Thread->WaitForCompletion(); } FCriticalSection* FPrimeNumberWorker::GetCriticalSection() { return &QueueCritical; } void FPrimeNumberWorker::Shutdown() { if (Runnable) { Runnable->EnsureCompletion(); delete Runnable; Runnable = nullptr; } } bool FPrimeNumberWorker::IsThreadFinished() { if (Runnable) return Runnable->IsFinished(); return true; } int32 FPrimeNumberWorker::FindNextPrimeNumber() { int32 TestPrime = 123; return TestPrime; }
-
再来个测试方法,写在GameInstanece的扩展类中(习惯了测试用 console命令调方法,不懂的看这里 ue4-控制台执行方法)
UFUNCTION(Exec) virtual void MyAsyncSuspend(); UFUNCTION(Exec) virtual void MyAsyncResume(); UFUNCTION(Exec) virtual void MyAsyncThread(); void UMyGameInstance::MyAsyncSuspend() { FPrimeNumberWorker::Get()->Suspend(); } void UMyGameInstance::MyAsyncResume() { FPrimeNumberWorker::Get()->Resume(); } void UMyGameInstance::MyAsyncThread() { mNumVec.Empty(); FPrimeNumberWorker::JoyInit(mNumVec, this); GetTimerManager().SetTimer(mTimer1, [&]()->void { FPrimeNumberWorker* pnw = FPrimeNumberWorker::Get(); if (!pnw) return; FCriticalSection* cs = pnw->GetCriticalSection(); //获取FPrimeNumberWorker到中的互斥锁QueueCritical FScopeLock QueueLock(cs);//锁住,等作用域过后QueueLock自动析构解锁 UE_LOG(LogMyTest, Warning, TEXT("--- UMyGameInstance::MyAsyncThread, mNumVec.Num=%d"), mNumVec.Num()); if (pnw->IsThreadFinished()) FPrimeNumberWorker::Shutdown(); }, 1.0f, true); }
- 测试互斥。 上面的 FPrimeNumberWorker 里面的代码方法是有加锁的,在控制台输入指令
MyAsyncThread
可测试。注释掉FScopeLock QueueLock(cs);
这一行代码就能看出没有互斥锁的区别。 - 测试线程的悬挂和唤醒。
- 取消注释 FPrimeNumberWorker 里面的
//Suspend();
- 注释掉 MyAsyncThread 测试方法中的的定时器
GetTimerManager
(因为其他线程中在 未解锁 的 情况下 悬挂 的话,主线程获取不到这个锁会一直等待,也就是看起来像卡死了) - 在控制台输入指令
MyAsyncThread
,就可以 Run 方法的线程悬挂住该线程 - 在控制台输入指令
MyAsyncResume
,就可以 唤醒之前调用ThreadSuspendedEvent->Wait();
的线程。
- 取消注释 FPrimeNumberWorker 里面的
- 测试互斥。 上面的 FPrimeNumberWorker 里面的代码方法是有加锁的,在控制台输入指令
线程唤醒、悬挂
- FEvent* ThreadSuspendedEvent;
- 线程悬挂。A线程中调用
ThreadSuspendedEvent->Wait();
,A线程就会悬挂主,必须由B线程调用ThreadSuspendedEvent->Trigger();
才能唤醒A线程。 - 所以如果直接在主线程(GameThread)中调用
ThreadSuspendedEvent->Wait();
,那就看起像卡死了。
编辑器模式下进行多线程开发注意事项
-
PIE模式下,与新开的线程交互正常;如果退出PIE模式,PIE中的实例对象都会被标记为坐等回收的对象 InValid,此时如果新开的线程还在Run中跑用到PIE中的实例对象,将造成 编辑器崩溃。
-
最好在你的游戏退出时 FPrimeNumberWorker ShutDown一下,方便下次测试。
void UMyGameInstance::Shutdown() { FPrimeNumberWorker::Shutdown(); Super::Shutdown(); }
参考资料
更多推荐
所有评论(0)