Skip to content

UE5 C++ 멀티스레딩 가이드

개요 — UE 멀티스레딩의 필요성과 제약

Section titled “개요 — UE 멀티스레딩의 필요성과 제약”

CPU 집약적 작업(파일 IO, 경로 탐색, 물리 계산)을 게임 스레드(Game Thread)에서 처리하면 프레임 드랍이 발생합니다. 백그라운드 스레드로 분리해 게임 스레드를 해방시켜야 합니다.

핵심 제약:

  • UObject, Actor, Component는 반드시 게임 스레드에서만 접근해야 합니다.
  • 렌더 명령은 렌더 스레드에서만 실행합니다.
  • 백그라운드 작업 완료 후 게임 스레드로 결과를 전달할 때는 AsyncTask(ENamedThreads::GameThread, ...)를 사용합니다.

방법적합한 상황복잡도
FRunnable장기 실행 루프 (서버 연결, 스트리밍)중간
AsyncTask / Async일회성 백그라운드 작업낮음
TaskGraph의존성 있는 작업 그래프높음
TFuture / TPromise비동기 결과 반환낮음

FRunnable은 독립 스레드에서 루프를 실행하는 가장 저수준 방법입니다.

FileLoaderRunnable.h
#pragma once
#include "CoreMinimal.h"
#include "HAL/Runnable.h"
class FFileLoaderRunnable : public FRunnable
{
public:
FFileLoaderRunnable(const FString& FilePath, TFunction<void(TArray<uint8>)> OnComplete);
virtual ~FFileLoaderRunnable();
// FRunnable 인터페이스
virtual bool Init() override;
virtual uint32 Run() override; // 스레드 메인 함수
virtual void Stop() override; // 외부에서 중단 요청
virtual void Exit() override; // 스레드 종료 후 정리
void EnsureCompletion();
private:
FString FilePath;
TFunction<void(TArray<uint8>)> OnCompleteCallback;
FRunnableThread* Thread = nullptr;
FThreadSafeCounter StopRequested; // 원자적 플래그
};
FileLoaderRunnable.cpp
#include "FileLoaderRunnable.h"
#include "HAL/RunnableThread.h"
#include "Async/AsyncWork.h"
#include "Misc/FileHelper.h"
FFileLoaderRunnable::FFileLoaderRunnable(const FString& InPath,
TFunction<void(TArray<uint8>)> InCallback)
: FilePath(InPath)
, OnCompleteCallback(InCallback)
{
// 스레드 생성 (자동 실행 시작)
Thread = FRunnableThread::Create(this, TEXT("FileLoaderThread"),
0, TPri_BelowNormal);
}
FFileLoaderRunnable::~FFileLoaderRunnable()
{
if (Thread)
{
Thread->Kill(true);
delete Thread;
Thread = nullptr;
}
}
bool FFileLoaderRunnable::Init()
{
return true; // 초기화 성공 시 Run() 진입
}
uint32 FFileLoaderRunnable::Run()
{
// 백그라운드 스레드에서 파일 로드
TArray<uint8> FileData;
if (!StopRequested.GetValue() && FFileHelper::LoadFileToArray(FileData, *FilePath))
{
// 완료 후 게임 스레드로 결과 전달 (중요!)
AsyncTask(ENamedThreads::GameThread, [this, Data = MoveTemp(FileData)]() mutable
{
// 게임 스레드에서 콜백 실행
if (OnCompleteCallback)
{
OnCompleteCallback(MoveTemp(Data));
}
});
}
return 0;
}
void FFileLoaderRunnable::Stop()
{
StopRequested.Set(1); // 원자적 중단 플래그
}
void FFileLoaderRunnable::Exit()
{
// 스레드 종료 후 정리 작업
}
void FFileLoaderRunnable::EnsureCompletion()
{
Stop();
if (Thread)
{
Thread->WaitForCompletion();
}
}
// 사용 예시
void AMyActor::BeginPlay()
{
Super::BeginPlay();
FileLoader = MakeUnique<FFileLoaderRunnable>(TEXT("D:/data.bin"),
[this](TArray<uint8> Data)
{
// 이미 게임 스레드에서 실행됨
UE_LOG(LogTemp, Log, TEXT("File loaded: %d bytes"), Data.Num());
ProcessData(MoveTemp(Data));
});
}
void AMyActor::EndPlay(const EEndPlayReason::Type Reason)
{
if (FileLoader)
{
FileLoader->EnsureCompletion();
FileLoader.Reset();
}
Super::EndPlay(Reason);
}

3. AsyncTask / Async — 일회성 비동기 작업

Section titled “3. AsyncTask / Async — 일회성 비동기 작업”
#include "Async/AsyncWork.h"
// 백그라운드 스레드로 작업 전송
AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, []()
{
// 백그라운드 스레드에서 실행
int32 Result = HeavyComputation();
// 게임 스레드로 결과 반환
AsyncTask(ENamedThreads::GameThread, [Result]()
{
// 게임 스레드에서 UObject 안전 접근
UE_LOG(LogTemp, Log, TEXT("Result: %d"), Result);
});
});
#include "Async/Async.h"
// TFuture 반환 — 결과를 나중에 받을 수 있음
TFuture<int32> Future = Async(EAsyncExecution::ThreadPool, []() -> int32
{
return HeavyComputation(); // 백그라운드에서 실행
});
// 게임 스레드에서 결과 사용 (블로킹)
// int32 Result = Future.Get();
// 비블로킹: Then으로 완료 콜백
Future.Next([](int32 Result)
{
// 별도 스레드에서 실행될 수 있음 — UObject 접근 주의
UE_LOG(LogTemp, Log, TEXT("Async result: %d"), Result);
});

3.3 FAsyncTask — 작업 클래스 기반

Section titled “3.3 FAsyncTask — 작업 클래스 기반”
PathfindingTask.h
class FPathfindingTask : public FNonAbandonableTask
{
friend class FAutoDeleteAsyncTask<FPathfindingTask>;
public:
FPathfindingTask(FVector Start, FVector Goal,
TFunction<void(TArray<FVector>)> Callback)
: StartPos(Start), GoalPos(Goal), OnComplete(Callback) {}
void DoWork()
{
// 백그라운드에서 경로 탐색
TArray<FVector> Path = ComputePath(StartPos, GoalPos);
// 게임 스레드로 결과 전달
AsyncTask(ENamedThreads::GameThread,
[this, Path = MoveTemp(Path)]() mutable
{
if (OnComplete) OnComplete(MoveTemp(Path));
});
}
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FPathfindingTask, STATGROUP_ThreadPoolAsyncTasks);
}
private:
FVector StartPos;
FVector GoalPos;
TFunction<void(TArray<FVector>)> OnComplete;
TArray<FVector> ComputePath(FVector Start, FVector Goal) { return {}; } // 구현
};
// 사용
void AMyAI::RequestPath(FVector Start, FVector Goal)
{
// FAutoDeleteAsyncTask: 완료 후 자동 삭제
(new FAutoDeleteAsyncTask<FPathfindingTask>(Start, Goal,
[this](TArray<FVector> Path)
{
// 게임 스레드에서 호출됨
NavigatePath(Path);
}))->StartBackgroundTask();
}

FCriticalSection DataLock;
TArray<int32> SharedData;
// 쓰기 (다른 스레드에서)
{
FScopeLock Lock(&DataLock); // 스코프 내 자동 잠금/해제
SharedData.Add(42);
}
// 읽기 (게임 스레드에서)
{
FScopeLock Lock(&DataLock);
for (int32 Value : SharedData)
{
UE_LOG(LogTemp, Log, TEXT("%d"), Value);
}
}

4.2 FThreadSafeCounter — 원자적 정수

Section titled “4.2 FThreadSafeCounter — 원자적 정수”
FThreadSafeCounter PendingTaskCount;
// 작업 시작 시
PendingTaskCount.Increment();
// 작업 완료 시
if (PendingTaskCount.Decrement() == 0)
{
// 모든 작업 완료
AsyncTask(ENamedThreads::GameThread, [this]()
{
OnAllTasksComplete();
});
}
FEvent* ReadyEvent = FPlatformProcess::GetSynchEventFromPool(false);
// 스레드 A: 신호 대기
ReadyEvent->Wait();
// 스레드 B: 신호 전송
ReadyEvent->Trigger();
// 사용 후 반환
FPlatformProcess::ReturnSynchEventToPool(ReadyEvent);

// 현재 게임 스레드인지 확인
void UMyObject::SafeUpdate()
{
if (!IsInGameThread())
{
// 게임 스레드로 전달
AsyncTask(ENamedThreads::GameThread, [this]()
{
SafeUpdate();
});
return;
}
// 이 이하는 게임 스레드에서 실행됨
// UObject / Actor 접근 안전
SomeActor->SetActorLocation(NewLocation);
}

기법적합한 상황
FRunnable스트리밍·서버 연결 등 지속 루프
Async / AsyncTask일회성 헤비 계산 (경로, 파싱)
FAsyncTask재사용 가능한 작업 단위 클래스
FCriticalSection공유 데이터 보호
FThreadSafeCounter원자적 카운터 (진행 추적)

핵심 규칙:

  • UObject·Actor는 게임 스레드에서만 접근 — 백그라운드에서 UObject를 건드리면 크래시
  • 백그라운드 결과를 게임 스레드로 전달할 때는 반드시 AsyncTask(ENamedThreads::GameThread, ...) 사용
  • FRunnableThread 소멸 전 WaitForCompletion()으로 완전히 종료 확인