概述
- UE4里,提供的多線程的方法:
- 繼承
FRunnable接口創建單個線程 - 創建
AsyncTask調用線程池里面空閑的線程 - 通過
TaskGraph系統來異步完成一些自定義任務 - 支持原生的多線程
std::thread
- 繼承
- 在GameThread線程之外的其他線程中
- 不要 spawning / modifying / deleting UObjects / AActors
- 不要使用定時器 TimerManager
- 不要使用任何繪制接口,例如 DrawDebugLine,然有可能崩潰
- 如果想做的話,可以在主線程中異步處理
- 其他線程中一般做數據收發和解析,數學運算等
本文主要介紹 FRunnable 類
FRunnable
-
FRunnable是UE4中多線程的實現方式之一,適用於復雜運算
-
FRunnable 是線程的執行體,提供相應的接口。FRunnable需要依附與一個FRunnableThread對象,才能被執行
class CORE_API FRunnable { public: // .... virtual bool Init(); // 初始化 runnable 對象,在FRunnableThread創建線程對象后調用 virtual uint32 Run() = 0; // Runnable 對象邏輯處理主體,在Init成功后調用 virtual void Stop() {} // 停止 runnable 對象, 線程提前終止時被用戶調用 virtual void Exit() {} // 退出 runnable 對象,由FRunnableThread調用 }; -
FRunnableThread 表示一個可執行的線程,該類會派生出平台相關的子類。通過調用
FRunnableThread::Create完成線程的創建

快速創建一個線程
創建 FRunnable 派生類
// .h
class TIPS_API FSimpleRunnable: public FRunnable
{
public:
FSimpleRunnable(const FString& ThreadName);
~FSimpleRunnable();
void PauseThread(); // 線程掛起 方法一
void WakeUpThread(); // 線程喚醒 方法一
void Suspend(bool bSuspend); // 線程掛起/喚醒 方法二
void StopThread(); // 停止線程,一般用該方法
void ShutDown(bool bShouldWait);// 停止線程,bShouldWait true的時候可強制 kill 線程
private:
FString m_ThreadName;
int32 m_ThreadID;
bool bRun = true; // 線程循環標志
bool bPause = false; //線程掛起標志
FRunnableThread* ThreadIns; // 線程實例
FEvent* ThreadEvent; //FEvent指針,掛起/激活線程, 在各自的線程內使用
virtual bool Init() override;
virtual uint32 Run() override;
virtual void Stop() override;
virtual void Exit() override;
};
// .cpp
FSimpleRunnable::FSimpleRunnable(const FString& ThreadName)
{
// 獲取 FEvent 指針
ThreadEvent = FPlatformProcess::GetSynchEventFromPool();
// 創建線程實例
m_ThreadName = ThreadName;
ThreadIns = FRunnableThread::Create(this, *m_ThreadName, 0, TPri_Normal);
m_ThreadID = ThreadIns->GetThreadID();
UE_LOG(LogTemp, Warning, TEXT("Thread Start! ThreadID = %d"), m_ThreadID);
}
FSimpleRunnable::~FSimpleRunnable()
{
if (ThreadEvent) // 清空 FEvent*
{
FPlatformProcess::ReturnSynchEventToPool(ThreadEvent); // delete ThreadEvent;
ThreadEvent = nullptr;
}
if (ThreadIns) // 清空 FRunnableThread*
{
delete ThreadIns;
ThreadIns = nullptr;
}
}
bool FSimpleRunnable::Init()
{
return true; //若返回 false ,線程創建失敗,不會執行后續函數
}
uint32 FSimpleRunnable::Run()
{
int32 count = 0;
FPlatformProcess::Sleep(0.03f); //延時,等待初始化完成
while (bRun)
{
if (bPause)
{
ThreadEvent->Wait(); // 線程掛起
if (!bRun) // 線程掛起時執行線程結束
{
return 0;
}
}
UE_LOG(LogTemp, Warning, TEXT("ThreadID: %d, Count: %d"),m_ThreadID, count);
count++;
FPlatformProcess::Sleep(0.1f); // 執行間隔,防止堵塞
}
return 0;
}
void FSimpleRunnable::Stop()
{
bRun = false;
bPause = false;
if (ThreadEvent)
{
ThreadEvent->Trigger(); // 保證線程不掛起
}
Suspend(false); // 保證線程不掛起,本例只是為了暫時不同的掛起方法,如果不使用Suspend(),無需使用
}
void FSimpleRunnable::Exit()
{
UE_LOG(LogTemp, Warning, TEXT("Thread Exit!"));
}
void FSimpleRunnable::PauseThread()
{
bPause = true;
UE_LOG(LogTemp, Warning, TEXT("Thread Pause!"));
}
void FSimpleRunnable::WakeUpThread()
{
bPause = false;
if (ThreadEvent)
{
ThreadEvent->Trigger(); // 喚醒線程
}
UE_LOG(LogTemp, Warning, TEXT("Thread Wakeup!"));
}
void FSimpleRunnable::Suspend(bool bSuspend)
{
if (ThreadIns)
{
ThreadIns->Suspend(bSuspend); //掛起/喚醒
}
}
void FSimpleRunnable::StopThread()
{
Stop();
ThreadIns->WaitForCompletion(); // 等待線程執行完畢
}
void FSimpleRunnable::ShutDown(bool bShouldWait)
{
if (ThreadIns)
{
ThreadIns->Kill(bShouldWait); // bShouldWait 為false,Suspend(true)時,會崩
}
}
創建調用多線程的Actor
// .h
protected:
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
private:
FSimpleRunnable* SimpleRunnable;
public:
UFUNCTION(BlueprintCallable)
void CreateNewThread(const FString& ThreadName);
UFUNCTION(BlueprintCallable)
void PauseThread();
UFUNCTION(BlueprintCallable)
void SuspendThread(bool bSuspend);
UFUNCTION(BlueprintCallable)
void WakeUpThread();
UFUNCTION(BlueprintCallable)
void StopThread();
UFUNCTION(BlueprintCallable)
void ForceKillThread(bool bShouldWait);
};
// .cpp
void ARunnableActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
if (SimpleRunnable) // 防止線程掛起,退出無響應
{
SimpleRunnable->StopThread();
delete SimpleRunnable;
SimpleRunnable = nullptr;
}
}
void ARunnableActor::CreateNewThread(const FString& ThreadName)
{
SimpleRunnable = new FSimpleRunnable(ThreadName);
}
void ARunnableActor::PauseThread()
{
if (SimpleRunnable)
{
SimpleRunnable->PauseThread();
}
}
void ARunnableActor::SuspendThread(bool bSuspend)
{
if (SimpleRunnable)
{
SimpleRunnable->Suspend(bSuspend);
}
}
void ARunnableActor::WakeUpThread()
{
if (SimpleRunnable)
{
SimpleRunnable->WakeUpThread();
}
}
void ARunnableActor::StopThread()
{
if (SimpleRunnable)
{
SimpleRunnable->StopThread();
}
}
void ARunnableActor::ForceKillThread(bool bShouldWait)
{
if (SimpleRunnable)
{
SimpleRunnable->ShutDown(bShouldWait);
delete SimpleRunnable;
SimpleRunnable = nullptr;
}
}

單例線程
- 當希望線程只能創建一次時,可以通過聲明靜態單例FRunnable (本例為FSimpleRunnable)
// .h
static FSimpleRunnable* MySimpleRunnable; // 聲明靜態單例
static FSimpleRunnable* JoyInit(); // 聲明靜態方法
// cpp
// 初始化靜態單例
FSimpleRunnable* FSimpleRunnable::MySimpleRunnable = nullptr;
//創建 SimpleRunnable 實例
FSimpleRunnable* FSimpleRunnable::JoyInit()
{
if (!MySimpleRunnable && FPlatformProcess::SupportsMultithreading())
{
MySimpleRunnable = new FSimpleRunnable();
}
return MySimpleRunnable;
}
多個線程
當希望執行多個線程時
- 可用TMap<Name, FRunnable > 存儲,移除
- 也可設定線程結束條件,讓其自行結束線程
線程鎖
UE4 線程鎖包括:
- FSpinLock 自旋鎖
- FScopeLock 區域鎖
- FCriticalSection 臨界區
- FRWLock 讀寫鎖
本文使用 FScopeLock 、FCriticalSection 作為測試
不使用線程鎖
本例使用兩個線程 為同一個整數做加法,知道該整數到達目標值
-
修改 SimpleRunnable 代碼
FSimpleRunnable(const FString& ThreadName, int32* CurrentNumber, int32 MaxNumber); int32* m_CurrentNumber; int32 m_MaxNumber; int32 m_CalcCount = 0;FSimpleRunnable::FSimpleRunnable(const FString& ThreadName, int32* CurrentNumber, int32 MaxNumber) { /* 省略部分代碼 */ m_CurrentNumber = CurrentNumber; m_MaxNumber = MaxNumber; /* 省略部分代碼 */ } uint32 FSimpleRunnable::Run() { FPlatformProcess::Sleep(0.03f); //延時,等待初始化完成 while (bRun && *m_CurrentNumber<m_MaxNumber) { /* 省略部分代碼 */ (*m_CurrentNumber)++; m_CalcCount++; if (m_CalcCount % 100 == 0) { UE_LOG(LogTemp, Warning, TEXT("ThreadID: %d, CurrentNumber: %d"),m_ThreadID, *m_CurrentNumber); } FPlatformProcess::Sleep(0.0001f); // 執行間隔,防止堵塞 } return 0; } void FSimpleRunnable::Exit() { UE_LOG(LogTemp, Warning, TEXT("Thread Exit! ThreadID: %d, CurrentNumber: %d, CalcCount: %d"),m_ThreadID, *m_CurrentNumber, m_CalcCount); } -
修改 RunnableActor 代碼
UPROPERTY(EditAnywhere) int32 m_MaxNumber = 1000;void ARunnableActor::CreateNewThread(const FString& ThreadName) { SimpleRunnable = new FSimpleRunnable(TEXT("Thread1"), &m_CurrentNumber, m_MaxNumber); SimpleRunnable = new FSimpleRunnable(TEXT("Thread2"), &m_CurrentNumber, m_MaxNumber); }
使用線程鎖
- 注意 FCriticalSection 是否使用 static 聲明
FScopeLock
-
方法一
修改 SimpleRunnable 代碼
{ // 注意這個作用域用於 **FScopeLock** static FCriticalSection m_mutex; //聲明 staic 可以讓線程之間互鎖 FScopeLock ScopeLock(&m_mutex); // 該作用域內上鎖 (*m_CurrentNumber)++; } m_CalcCount++; -
方法二
修改 SimpleRunnable 代碼
static FCriticalSection m_mutex; //聲明 staic 可以讓線程之間互鎖 FScopeLock* ScopeLock = new FScopeLock(&m_mutex); // 上鎖 (*m_CurrentNumber)++; delete ScopeLock; // 解鎖
FCriticalSection Lock()/UnLock()
修改 SimpleRunnable 代碼
// 放在類聲明static ,使用 Lock() 編譯不通過
// static 可以讓線程之間互鎖,不使用 static 鎖不生效
// 不使用 static,線程內可以上鎖。可以在類中聲明
static FCriticalSection m_mutex;
m_mutex.Lock(); // 上鎖
(*m_CurrentNumber)++;
m_mutex.Unlock(); // 解鎖

