Multithreading
Overview
In the Unreal Engine, the main thread is known as the Game Thread.
There are multiple ways to do multithreading work in the Unreal Engine.
- Async Tasks
- Runnable Threads
- Queued Thread Pools
Platforms
To determine if the target supports multithreading, call the FPlatformProcess::SupportsMultithreading function.
The HTML5 platform is single-thread when compiled without support for Emscripten pthreads (POSIX Threads).
Other platforms can be compiled with a single-threaded model (DEFAULT_NO_THREADING symbol).
Additionally, single-threaded can be enforced with the "-nothreading" mode set in the command-line.
Queued Thread Pools
Queued thread pools are represented by the FQueuedThreadPool interface.
It used as a callback by FQueuedThreads to queue asynchronous work for callers using background threads.
There is a global thread pool for shared async operations called GThreadPool.
API
To create a queue thread pool, call the Create function.
bool Create(
    uint32 InNumQueuedThreads,
    uint32 StackSize,
    EThreadPriority ThreadPriority
);- InNumQueuedThreads– The number of threads to use in the pool.
- StackSize– The size of stack, in bytes used by the threads in the pool (default is 32k).
- ThreadPriority– The priority of the threads in the pool (default is- TPri_Normal).
To destroy a queue thread pool, call the Destroy function.
The function releases all the threads in the pool.
void Destroy();To add a thread to the pool, call the AddQueuedWork function with a IQueuedWork object.
void AddQueuedWork( IQueuedWork* InQueuedWork );To remove a thread from the pool, call the RetractQueuedWork function with a IQueuedWork object.
bool RetractQueuedWork( IQueuedWork* InQueuedWork );Internals
If the client supports multithreading (FPlatformProcess::SupportsMultithreading), the GThreadPool global thread pool is created in FEngineLoop::PreInit.
The pool is allocated with the FQueuedThreadPool::Allocate function.
GThreadPool = FQueuedThreadPool::Allocate();Then created with the FQueuedThreadPool::Create function.
- The number of threads is determined by FPlatformMisc::NumberOfWorkerThreadsToSpawn.ℹ️Dedicated servers have only one thread in the pool.
- The stack is 128k.
- The priority of the treads is TPri_Lowest.
int32 NumThreadsInThreadPool = FPlatformMisc::NumberOfWorkerThreadsToSpawn();
GBackgroundPriorityThreadPool->Create(NumThreadsInThreadPool, 128 * 1024, TPri_Lowest);GThreadPool is released in FEngineLoop::AppPreExit.
if (GThreadPool != nullptr)
{
	  GThreadPool->Destroy();
}Usage
- The global thread pool GThreadPool.
- The sound file loading pool FileLoadingThreadPool.
- The distance field atlas builder FDistanceFieldAsyncQueue(Editor only).
GThreadPool is used to:
- Version control operations (Editor only).
- The encoding of the shadow map textures.
- Audio decompression.
- Texture streaming.
Runnable Threads
Runnable Objects
Runnable objects are represented by the FRunnable interface.
A runnable object is executed on an arbitrary thread.
A runnable object is implemented by overriding the following functions:
- Init– Initializes the thread.
- Run– The threaded work is performed.
- Stop– Requests to terminate the thread early.
- Exit– Cleans up the thread.
Threads
Runnable threads are represented by the FRunnableThread interface.
To create a runnable thread, call the Create function.
You can specify the runnable, the stack size and thread priority.
static FRunnableThread * Create
(
    class FRunnable * InRunnable,
    const TCHAR * ThreadName,
    uint32 InStackSize,
    EThreadPriority InThreadPri,
    uint64 InThreadAffinityMask
)Example
Declare a custom runnable type.
class FMyRunnable : public FRunnable
{
public:
    virtual bool Init() override;
    virtual uint32 Run() override;
    virtual void Stop() override;
    virtual void Exit() override;
};Implement the FRunnable functions.
bool FMyRunnable::Init()
{
    ...
}
uint32 FMyRunnable::Run()
{
    ...
}
void FMyRunnable::Stop()
{
    ...
}
void FMyRunnable::Exit()
{
    ...
}Declare variables to store the runnable object and runnable thread.
FMyRunnable* MyRunnable = nullptr;
FRunnableThread* MyThread = nullptr;Create the runnable.
if (FPlatformProcess::SupportsMultithreading())
{
		MyRunnable = new FMyRunnable();
		MyThread = FRunnableThread::Create(FMyRunnable, TEXT("My Thread"));
}Usages
- Thread Heartbeat, to check for hangs (FThreadHeartBeat).
- Audio thread (FAudioThread).
- TCP Listener (FTcpListener).
- UDP Socket Sender and Receiver (FUdpSocketSender,FUdpSocketReceiver).
Async Tasks
Async tasks are the most common usage of multithreading.
There are two types of async tasks: FAsyncTask and FAutoDeleteAsyncTask.
The only difference is that FAutoDeleteAsyncTask can delete itself automatically when the task is complete.
Create
To create a new async task, declare a new class that derive from FNonAbandonableTask.
This class performs the threaded work.
Declare this class as a friend class of FAsyncTask using the type of the class as the template argument.
Define the DoWork function that will be called when the task is running.
Define the GetStatId method.
Run
To start an async task:
- Call the StartBackgroundTaskfunction to queue the task in the background thread pool.void StartBackgroundTask(FQueuedThreadPool * InQueuedPool);
- Or call the StartSynchronousTaskfunction to run the task on the current thread.void StartSynchronousTask();
Example
Async Task
- Declare a custom task class that derive from FNonAbandonableTask.
- Declare the class as a friend class of FAsyncTask.
- Implement the DoWorkfunction to perform the threaded work.
- Include the GetStatIdinline function.class FMyTask : public FNonAbandonableTask { friend class FAsyncTask<FMyTask>; protected: void DoWork() { ... } FORCEINLINE TStatId GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(FMyTask, STATGROUP_ThreadPoolAsyncTasks); } };
- Declare the task as a member of the owner type.FAsyncTask<FMyTask> * MyTask;
- Create and run the task.- Create an instance of FAsyncTaskfor the task.
 - Start the task by calling the StartBackgroundTaskfunction.
 MyTask = new FAsyncTask<FMyTask>(); MyTask->StartBackgroundTask();
- Create an instance of 
- When the owner type is destroyed, clean up the task.- Try to cancel the task by calling the Cancelfunction.
 - If it fails, call the EnsureCompletionfunction to wait until the task is complete.
 - Delete the task.
 if (MyTask && !MyTask->Cancel()) { MyTask->EnsureCompletion(); } delete MyTask; MyTask = nllptr;
- Try to cancel the task by calling the 
Auto Delete Async Task
- Declare a custom task class as a friend class of FAutoDeleteAsyncTask.class FMyTask : public FNonAbandonableTask { friend class FAutoDeleteAsyncTask<FMyTask>; protected: void DoWork() { ... } FORCEINLINE TStatId GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(FMyTask, STATGROUP_ThreadPoolAsyncTasks); } };
- Create an instance of FAutoDeleteAsyncTaskfor the task.
- Start the task by calling the StartBackgroundTaskfunction.
(new FAutoDeleteAsyncTask<FMyTask>())->StartBackgroundTask();Sleep
The FGenericPlatformProcess::Sleep function sleeps the current thread for a specific number of seconds. The function uses Stats (FThreadIdleStats).
0.0 seconds releases the current time slice to let other threads get some attention.
The FGenericPlatformProcess::SleepNoStats performs the same functionality without Stats.
Implementation
Sleep is implemented with the usleep function.
On Windows, sleep is implemented using the Sleep function with a value in milliseconds.
Best Practices
- Do not modify UObjectsin other threads (without synchronization).
- Do not use TimerManagerin other threads.
- Do not use Draw Debug Helpers functions in other threads.