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.

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
);

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.

int32 NumThreadsInThreadPool = FPlatformMisc::NumberOfWorkerThreadsToSpawn();

GBackgroundPriorityThreadPool->Create(NumThreadsInThreadPool, 128 * 1024, TPri_Lowest);

GThreadPool is released in FEngineLoop::AppPreExit.

if (GThreadPool != nullptr)
{
	  GThreadPool->Destroy();
}

Usage

GThreadPool is used to:

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:

  1. Init – Initializes the thread.
  1. Run – The threaded work is performed.
  1. Stop – Requests to terminate the thread early.
  1. 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

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:

Example

Async Task

  1. Declare a custom task class that derive from FNonAbandonableTask.
  1. Declare the class as a friend class of FAsyncTask.
  1. Implement the DoWork function to perform the threaded work.
  1. Include the GetStatId inline function.
    class FMyTask : public FNonAbandonableTask
    {
        friend class FAsyncTask<FMyTask>;
    
    protected:
        void DoWork()
        {
            ...
        }
    
        FORCEINLINE TStatId GetStatId() const
        {
            RETURN_QUICK_DECLARE_CYCLE_STAT(FMyTask, STATGROUP_ThreadPoolAsyncTasks);
        }
    };
  1. Declare the task as a member of the owner type.
    FAsyncTask<FMyTask> * MyTask;
  1. Create and run the task.
    1. Create an instance of FAsyncTask for the task.
    1. Start the task by calling the StartBackgroundTask function.
    MyTask = new FAsyncTask<FMyTask>();
    
    MyTask->StartBackgroundTask();
  1. When the owner type is destroyed, clean up the task.
    1. Try to cancel the task by calling the Cancel function.
    1. If it fails, call the EnsureCompletion function to wait until the task is complete.
    1. Delete the task.
    if (MyTask && !MyTask->Cancel())
    {
    	  MyTask->EnsureCompletion();
    }
    
    delete MyTask;
    MyTask = nllptr;

Auto Delete Async Task

  1. 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);
        }
    };
  1. Create an instance of FAutoDeleteAsyncTask for the task.
  1. Start the task by calling the StartBackgroundTask function.
(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