PrimeNumberWorker.h
class UMyGameInstance;
//~~~~~ Multi Threading ~~~
class FPrimeNumberWorker : public FRunnable
{
static FPrimeNumberWorker* Runnable;
FRunnableThread* Thread;
TArray<uint32>* PrimeNumbers;
AActor* mmyActor;
FThreadSafeCounter StopTaskCounter; //用于多线程间的判断交互,volatile int32 Counter;
int32 FindNextPrimeNumber();
FCriticalSection QueueCritical; //互斥锁
FEvent* ThreadSuspendedEvent; //线程悬挂和唤醒事件
public:
bool IsFinished();
void Suspend();
void Resume();
//Constructor / Destructor
FPrimeNumberWorker(TArray<uint32>& TheArray, AActor* myActor, const int32 PrimesFoundCount); //第三个变量为测试
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();
/*
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, AActor* myActor, const int32 PrimesFoundCount);//第三个变量为测试
static FPrimeNumberWorker* Get();
static void Shutdown();
static bool IsThreadFinished();
public:
int32 mPrimesFoundCount;
//~~~ Thread Core Functions ~~~
};
PrimeNumberWorker.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "TTTTT.h"
#include "PrimeNumberWorker.h"
//***********************************************************
//Thread Worker Starts as NULL, prior to being instanced
// This line is essential! Compiler error without it
FPrimeNumberWorker* FPrimeNumberWorker::Runnable = nullptr;
//***********************************************************
FPrimeNumberWorker::FPrimeNumberWorker(TArray<uint32>& TheArray, AActor* myActor, const int32 PrimesFoundCount)
: mmyActor(myActor)
, StopTaskCounter(0)
, mPrimesFoundCount(PrimesFoundCount)//Test
{
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();
//PrimeNumbers->Add(2);
//PrimeNumbers->Add(3);
if (mmyActor)
{
UE_LOG(LogTemp, Warning, TEXT("**********************************"));
UE_LOG(LogTemp, Warning, TEXT("Prime Number Thread Started!"));
UE_LOG(LogTemp, Warning, TEXT("**********************************"));
}
return true;
}
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())
{
FScopeLock* QueueLock = new FScopeLock(&QueueCritical); //锁住
//***************************************
//不要 spawning / modifying / deleting UObjects / AActors 等等之类的事
//这里做多线程间共享信息的 modify,如:PrimeNumbers->Add
//***************************************
PrimeNumbers->Add(FindNextPrimeNumber());
mPrimesFoundCount++;
//***************************************
//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.
UE_LOG(LogTemp, Warning, TEXT("%s"), *FString::FromInt(PrimeNumbers->Last()));
UE_LOG(LogTemp, Warning, TEXT("%d"), mPrimesFoundCount);
//***************************************
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//prevent thread from using too many resources
//FPlatformProcess::Sleep(0.01);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
PrimeNumbers->Add(FindNextPrimeNumber());
UE_LOG(LogTemp, Warning, TEXT("--- FPrimeNumberWorker::Run, lock"));
//Suspend();
//prevent thread from using too many resources
FPlatformProcess::Sleep(1.0f); //这里睡眠3秒是为了让GameThread中的AActor::MyAsyncThread中的日志打不出来
delete QueueLock;//解锁
UE_LOG(LogTemp, Warning, TEXT("--- FPrimeNumberWorker::Run, unlock"));
//FPlatformProcess::Sleep(2.0f); //这里睡眠2秒是为了让GameThread中获取到 互斥锁QueueCritical 并锁住
}
Stop();
//Run FPrimeNumberWorker::Shutdown() from the timer in Game Thread that is watching
//to see when FPrimeNumberWorker::IsThreadFinished()
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, AActor* myActor, const int32 PrimesFoundCount)
{
//Create new instance of thread if it does not exist
// and the platform supports multi threading!
if (!Runnable && FPlatformProcess::SupportsMultithreading())
{
Runnable = new FPrimeNumberWorker(TheArray, myActor, PrimesFoundCount);
}
bool isSupport = FPlatformProcess::SupportsMultithreading();
FString msg = isSupport ? "SupportsMultithread" : "dont SupportsMultithreading";
UE_LOG(LogTemp, 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;
}
ThreadActor.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "GameFramework/Actor.h"
#include "ThreadActor.generated.h"
UCLASS()
class TTTTT_API AThreadActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AThreadActor();
~AThreadActor();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
TArray<uint32> PrimeNumbers;
UFUNCTION(BlueprintCallable, Category = "My|ThreadActor")
void MyAsyncSuspend();
UFUNCTION(BlueprintCallable, Category = "My|ThreadActor")
void MyAsyncResume();
UFUNCTION(BlueprintCallable, Category = "My|ThreadActor")
void MyAsyncThread();
void BeginDestroy() override;
};
ThreadActor.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "TTTTT.h"
#include "ThreadActor.h"
#include "PrimeNumberWorker.h"
// Sets default values
AThreadActor::AThreadActor()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
}
AThreadActor::~AThreadActor()
{
//FPrimeNumberWorker::Shutdown();
}
// Called when the game starts or when spawned
void AThreadActor::BeginPlay()
{
Super::BeginPlay();
//FPrimeNumberWorker::JoyInit(PrimeNumbers, 50000, GetWorld()->GetFirstPlayerController());
}
// Called every frame
void AThreadActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void AThreadActor::MyAsyncSuspend()
{
FPrimeNumberWorker::Get()->Suspend();
}
void AThreadActor::MyAsyncResume()
{
FPrimeNumberWorker::Get()->Resume();
}
void AThreadActor::MyAsyncThread()
{
FTimerHandle mTimer;
PrimeNumbers.Empty();
FPrimeNumberWorker::JoyInit(PrimeNumbers, this, 500);
GetWorldTimerManager().SetTimer(mTimer, [&]()->void {
FPrimeNumberWorker* pnw = FPrimeNumberWorker::Get();
if (!pnw) return;
FCriticalSection* cs = pnw->GetCriticalSection(); //获取FPrimeNumberWorker到中的互斥锁QueueCritical
//FScopeLock QueueLock(cs);//锁住,等作用域过后QueueLock自动析构解锁 卡住
UE_LOG(LogTemp, Warning, TEXT("--- AMyActor::MyAsyncThread, PrimeNumbers.Num=%d"), PrimeNumbers.Num());
if (pnw->IsThreadFinished())
FPrimeNumberWorker::Shutdown();
}, 1.0f, true);
}
void AThreadActor::BeginDestroy()
{
Super::BeginDestroy();
FPrimeNumberWorker::Shutdown();
UE_LOG(LogTemp, Warning, TEXT("Game exit!"));
}
多线程Task:
Actor.h
/* Calculates prime numbers in the game thread */
UFUNCTION(BlueprintCallable, Category = MultiThreading)
void CalculatePrimeNumbers();
/* Calculates prime numbers in a background thread */
UFUNCTION(BlueprintCallable, Category = MultiThreading)
void CalculatePrimeNumbersAsync();
/* The max prime number */
UPROPERTY(EditAnywhere, Category = MultiThreading)
int32 MaxPrime;
Actor.cpp
#include "MyBlueprintFunctionLibrary.h"
#include "Runtime/Core/Public/Async/AsyncWork.h"
#include "ExampleAutoDeleteAsyncTask.h"
void AMyActor::CalculatePrimeNumbers()
{
UMyBlueprintFunctionLibrary::CalculatePrimeNumbers(MaxPrime);
}
void AMyActor::CalculatePrimeNumbersAsync()
{
auto task = new FAutoDeleteAsyncTask<ExampleAutoDeleteAsyncTask>(5);
if (task)
{
task->StartSynchronousTask();
}
}
MyBlueprintLibrary.h:
public:
UFUNCTION(BlueprintCallable, Category = "Damage")
static void CalculatePrimeNumbers(int32 UpperLimit);
MyBlueprintLibrary.cpp:
void UMainFunctionLibrary::CalculatePrimeNumbers(int32 UpperLimit)
{
//Calculating the prime numbers...
for (int32 i = 1; i <= UpperLimit; i++)
{
bool isPrime = true;
for (int32 j = 2; j <= i / 2; j++)
{
if (FMath::Fmod(i, j) == 0)
{
isPrime = false;
break;
}
}
if (isPrime)
GLog->Log("Prime number #" + FString::FromInt(i) + ": " + FString::FromInt(i));
}
}
MyTask.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "MyBlueprintFunctionLibrary.h"
#include "Runtime/Core/Public/Async/AsyncWork.h"
/**
*
*/
class PAKPROJ_API ExampleAutoDeleteAsyncTask : public FNonAbandonableTask
{
int32 MaxPrime;
public:
//friend class FAutoDeleteAsyncTask<ExampleAutoDeleteAsyncTask>;
ExampleAutoDeleteAsyncTask(int32 MaxPrime)
{
this->MaxPrime = MaxPrime;
}
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(ExampleAutoDeleteAsyncTask, STATGROUP_ThreadPoolAsyncTasks);
}
//核心接口, 这里便是任务的具体执行内容.
void DoWork()
{
UMyBlueprintFunctionLibrary::CalculatePrimeNumbers(MaxPrime);
}
};
上面是FAutoDeleteAsyncTask 下面是FAsyncTask的例子,FAsyncTask是需要手动删除的
FAsyncTask<ExampleAsyncTask>* MyTask = new FAsyncTask<ExampleAsyncTask>( 5 );
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 MyTask;
task graph
task.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include <Array.h>
#include <TaskGraphInterfaces.h>
//Multi thread Test, finding prime number
namespace VictoryMultiThreadTest
{
//Multi-threaded link to UObjects, do not create,modify,destroy UObjects / AActors via this link!
//AMyTaskActor* ThePC;
//~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~
// OUTPUT RESULTS OF TASK THREADS
TArray<int32> PrimeNumbers;
// This is the array of thread completions to know if all threads are done yet
FGraphEventArray VictoryMultithreadTest_CompletionEvents;
//~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~~~~~~~
//Are All Tasks Complete?
//~~~~~~~~~~~~~~~
bool TasksAreComplete()
{
//Check all thread completion events
for (int32 Index = 0; Index < VictoryMultithreadTest_CompletionEvents.Num(); Index++)
{
//If ! IsComplete()
if (!VictoryMultithreadTest_CompletionEvents[Index]->IsComplete())
{
return false;
}
}
return true;
}
//~~~~~~~~~~~
//Actual Task Code
//~~~~~~~~~~~
int32 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++)
{
if (TestPrime % b == 0)
{
NumIsPrime = false;
break;
//~~~
}
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//Did another thread find this number already?
if (PrimeNumbers.Contains(TestPrime))
{
return FindNextPrimeNumber(); //recursion
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//Success!
return TestPrime;
}
//~~~~~~~~~~~
//Each Task Thread
//~~~~~~~~~~~
class FVictoryTestTask
{
public:
FVictoryTestTask() //send in property defaults here
{
//can add properties here
}
/** return the name of the task **/
static const TCHAR* GetTaskName()
{
return TEXT("FVictoryTestTask");
}
FORCEINLINE static TStatId GetStatId()
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FVictoryTestTask, STATGROUP_TaskGraphTasks);
}
/** return the thread for this task **/
static ENamedThreads::Type GetDesiredThread()
{
return ENamedThreads::AnyThread;
}
/*
namespace ESubsequentsMode
{
enum Type
{
//Necessary when another task will depend on this task.
TrackSubsequents,
//Can be used to save task graph overhead when firing off a task that will not be a dependency of other tasks.
FireAndForget
};
}
*/
static ESubsequentsMode::Type GetSubsequentsMode()
{
return ESubsequentsMode::TrackSubsequents;
}
//~~~~~~~~~~~~~~~~~~~~~~~~
//Main Function: Do Task!!
//~~~~~~~~~~~~~~~~~~~~~~~~
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
PrimeNumbers.Add(FindNextPrimeNumber());
//***************************************
//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,
// which is AFTER all tasks have completed :)
//ThePC->ClientMessage(FString("A thread completed! ~ ") + FString::FromInt(PrimeNumbers.Last()));
UE_LOG(LogTemp, Warning, TEXT("A thread completed! ~ %s~"), *FString::FromInt(PrimeNumbers.Last()));
//***************************************
}
};
//~~~~~~~~~~~~~~~~~~~
// Multi-Task Initiation Point
//~~~~~~~~~~~~~~~~~~~
PRAGMA_DISABLE_OPTIMIZATION
void FindPrimes(const int32 TotalToFind)
{
PrimeNumbers.Empty();
PrimeNumbers.Add(2);
PrimeNumbers.Add(3);
//~~~~~~~~~~~~~~~~~~~~
//Add thread / task for each of total prime numbers to find
//~~~~~~~~~~~~~~~~~~~~
for (int32 b = 0; b < TotalToFind; b++)
{
VictoryMultithreadTest_CompletionEvents.Add(TGraphTask<FVictoryTestTask>::CreateTask(NULL, ENamedThreads::GameThread).ConstructAndDispatchWhenReady()); //add properties inside ConstructAndDispatchWhenReady()
}
}
PRAGMA_ENABLE_OPTIMIZATION
}
Actor.cpp
void AMyTaskActor::VictoryCheckAllThreadsDone()
{
if (VictoryMultiThreadTest::TasksAreComplete())
{
//Clear Timer
GetWorldTimerManager().ClearTimer(CountdownTimerHandle);
UE_LOG(LogTemp, Warning, TEXT("Multi Thread Test Done!"));
//UE_LOG(LogTemp, Warning, TEXT("--- AMyActor::MyAsyncThread,Num=%d"), PrimeNumbers.Num());
//UE_LOG(LogTemp, Warning, TEXT("%s"), *FString::FromInt(PrimeNumbers->Last()));
UE_LOG(LogTemp, Warning, TEXT("Prime Numbers Found:"));
for (int32 v = 0; v < VictoryMultiThreadTest::PrimeNumbers.Num(); v++)
{
UE_LOG(LogTemp, Warning, TEXT("%s~"), *FString::FromInt(VictoryMultiThreadTest::PrimeNumbers[v]));
}
}
else
{
UE_LOG(LogTemp, Warning, TEXT("not in settime"));
}
}
void AMyTaskActor::StartThreadTest()
{
//VictoryMultiThreadTest::ThePC = this;
VictoryMultiThreadTest::FindPrimes(50000); //first 50,000 prime numbers
//Start a timer to check when all the threads are done!
GetWorldTimerManager().SetTimer(CountdownTimerHandle,this,
&AMyTaskActor::VictoryCheckAllThreadsDone, 1.0f, true);
}
在GameThread线程之外的其他线程中,不允许做一下事情
- 不要 spawning / modifying / deleting UObjects / AActors
- 不要使用定时器 TimerManager
-
不要使用任何绘制接口,例如 DrawDebugLine
-
如果想在主线程中异步处理(也就是分帧处理),可以使用一下接口(在 Async.h 中)
AsyncTask(ENamedThreads::GameThread, [&]() { UE_LOG(LogMyTest, Warning, TEXT("--- UMyGameInstance::MyAsyncTask")); SpawnActor(3); });
- 1
- 2
- 3
- 4
-
- 测试互斥。 上面的 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(); }
参考:https://wiki.unrealengine.com/Multi-Threading:_How_to_Create_Threads_in_UE4
转载:http://blog.csdn.net/yangxuan0261/article/details/54574680
https://blog.csdn.net/noahzuo/article/details/51372972
https://blog.csdn.net/tuanxuan123/article/details/52780629
https://www.cnblogs.com/sevenyuan/p/9273065.html
https://wiki.unrealengine.com/Multi-Threading:_Task_Graph_System