[UE4 source code observation] observe the thread usage of UE4

0. starting point

I want to see which threads exist in the background when the UE4 editor is running and how they are created.
For this reason, when the project browsing interface appears, I set a breakpoint in Tick to prepare to observe the threads displayed in VS.

It should be noted that:
1. This observation is not stable. It may be different next time you open it (but there should be something with the same basis)
2. At this time, it is still in the project browser interface. After entering the editor, there should be more threads.

1. Inventory threads

There are 110 threads found at this time:

According to the information in this column, they can be divided into two categories:
The first type can show the specific running location, as shown in the figure above, many threads starting with TaskGraphThread and PoolThread are in Wait. The reason why you can see the location is that this is your own compiled engine, with pdb files, and the running location is in the UE4 engine code.
The second type is unable to display the location, such as the running location of ntdll.dll thread and Nvwgf2umx.dll thread in the figure below. It seems that they are not running in the UE4 engine code.

1.1 count threads in UE4 code

Count the threads in the UE4 code (i.e. the first category of threads in the above classification), 70 in total:

Main thread
TaskGraphThreadNP 0
TaskGraphThreadNP 1
TaskGraphThreadNP 2
TaskGraphThreadNP 3
TaskGraphThreadNP 4
TaskGraphThreadNP 5
TaskGraphThreadNP 6
TaskGraphThreadHP 7
TaskGraphThreadHP 8
TaskGraphThreadHP 9
TaskGraphThreadHP 10
TaskGraphThreadHP 11
TaskGraphThreadHP 12
TaskGraphThreadHP 13
TaskGraphThreadBP 14
TaskGraphThreadBP 15
TaskGraphThreadBP 16
TaskGraphThreadBP 17
TaskGraphThreadBP 18
TaskGraphThreadBP 19
TaskGraphThreadBP 20
PoolThread 0
PoolThread 1
PoolThread 2
PoolThread 3
PoolThread 4
PoolThread 5
PoolThread 6
PoolThread 7
PoolThread 8
PoolThread 9
PoolThread 10
PoolThread 11
PoolThread 12
PoolThread 13
PoolThread 14
PoolThread 15
PoolThread 16
PoolThread 17
PoolThread 18
PoolThread 19
PoolThread 20
PoolThread 21
PoolThread 22
PoolThread 23
PoolThread 24
OnlineAsyncTaskThreadNull DefaultInstance(1)
RenderThread 1
RTHeartBeat 1

It is observed that there are a large number of threads beginning with TaskGraphThread and PoolThread. It can be determined that they are two sets of thread systems of UE4, which are suitable for different occasions. But in addition, there are many threads with specific names, which are created separately.

1.2 count threads in non UE4 code

Count the threads in the following non UE4 code (i.e. the threads in the second category in the above classification), 40 in total:

ntdll.dll thread
ntdll.dll thread
ntdll.dll thread
SogouPY.ime thread
SogouPY.ime thread
SogouPY.ime thread
SogouPY.ime thread
SogouPY.ime thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
nvwgf2umx.dll thread
XAudio2_7.dll thread
combase.dll thread
mswsock.dll thread

I from wikidll Some dll related contents are checked:
Is an important Windows NT kernel level file
NVIDIA development, and graphics card driver.
Developed by Microsoft, related to Socket
Developed by Microsoft and related to COM(Component Object Model) of windows platform

2. Observe when it is created

2.1 name of thread

I have observed that most of the threads in UE4 have a clean name, while the threads in UE4 only display a dll name. So I think UE4 should deal with the name of the thread. After investigation, we found that it was true.
First, although the encapsulation is different, they all call the flannablethread:: Create function at the bottom:

 * Factory method to create a thread with the specified stack size and thread priority.
 * @param InRunnable The runnable object to execute
 * @param ThreadName Name of the thread
 * @param InStackSize The size of the stack to create. 0 means use the current thread's stack size
 * @param InThreadPri Tells the thread whether it needs to adjust its priority or not. Defaults to normal priority
 * @return The newly created thread or nullptr if it failed
static FRunnableThread* Create(
	class FRunnable* InRunnable,
	const TCHAR* ThreadName,
	uint32 InStackSize = 0,
	EThreadPriority InThreadPri = TPri_Normal,
	uint64 InThreadAffinityMask = FPlatformAffinity::GetNoAffinityMask() );

The second parameter, ThreadName, is the name of the thread seen during VS debugging.
According to this clue, you can search the flannablethread:: create character to see where the thread will be created in the source code:

2.2 taskgraph threads

The thread in TaskGraph is created by flannablethread:: create in its constructor, but the name of the thread has been calculated by some logic before. See \ Source\Runtime\Core\Private\Async\TaskGraph.cpp for details.

WorkerThreads[ThreadIndex].RunnableThread = FRunnableThread::Create(&Thread(ThreadIndex), *Name, StackSize, ThreadPri, Affinity); 

2.3 thread of ThreadPool

One difference between TaskGraph and TaskGraph is that TaskGraph only creates all threads in the first constructor, but the threads of ThreadPool are not all created in the first place (three times when I debugged)
Flannablethread:: create is called in the fquedthread:: Create function:

 * Creates the thread with the specified stack size and creates the various
 * events to be able to communicate with it.
 * @param InPool The thread pool interface used to place this thread back into the pool of available threads when its work is done
 * @param InStackSize The size of the stack to create. 0 means use the current thread's stack size
 * @param ThreadPriority priority of new thread
 * @return True if the thread and all of its initialization was successful, false otherwise
virtual bool Create(class FQueuedThreadPool* InPool,uint32 InStackSize = 0,EThreadPriority ThreadPriority=TPri_Normal)
	static int32 PoolThreadIndex = 0;
	const FString PoolThreadName = FString::Printf( TEXT( "PoolThread %d" ), PoolThreadIndex );

	OwningThreadPool = InPool;
	DoWorkEvent = FPlatformProcess::GetSynchEventFromPool();
	Thread = FRunnableThread::Create(this, *PoolThreadName, InStackSize, ThreadPriority, FPlatformAffinity::GetPoolThreadMask());
	return true;

The above function is called in fquedthreadpoolbase:: create:

for (uint32 Count = 0; Count < InNumQueuedThreads && bWasSuccessful == true; Count++)
		// Create a new queued thread
		FQueuedThread* pThread = new FQueuedThread();
		// Now create the thread and add it if ok
		if (pThread->Create(this,StackSize,ThreadPriority) == true)
			// Failed to fully create so clean up
			bWasSuccessful = false;
			delete pThread;

Fquedthreadpoolbase inherits from fquedthreadpool, which is an abstract class. Its definition of Create function is as follows:

* Creates the thread pool with the specified number of threads
 * @param InNumQueuedThreads Specifies the number of threads to use in the pool
 * @param StackSize The size of stack the threads in the pool need (32K default)
 * @param ThreadPriority priority of new pool thread
 * @return Whether the pool creation was successful or not
virtual bool Create( uint32 InNumQueuedThreads, uint32 StackSize = (32 * 1024), EThreadPriority ThreadPriority=TPri_Normal ) = 0;

Relevant codes can be found in the Core module

2.4 threads created separately

For example, RenderThread 1 and RTHeartBeat 1 are threads created separately:
The created code can be found in \ Source\Runtime\RenderCore\Private\RenderingThread.cpp:

GRenderingThread = FRunnableThread::Create(GRenderingThreadRunnable, *BuildRenderingThreadName(ThreadCount), 0, FPlatformAffinity::GetRenderingThreadPriority(), FPlatformAffinity::GetRenderingThreadMask());
GRenderingThreadHeartbeat = FRunnableThread::Create(GRenderingThreadRunnableHeartbeat, *FString::Printf(TEXT("RTHeartBeat %d"), ThreadCount), 16 * 1024, TPri_AboveNormal, FPlatformAffinity::GetRTHeartBeatMask());

2.5 thread not created by flannablethread in UE4

Although the threads created by using flannablethread:: create are discussed above, a small number of threads are not created by it, such as those found during inventory:


Their names are not clean. They don't look like the threads created by frennablethread:: create.
In addition, in the previous blog [UE4 source code observation] try to generate startup screen It is also found that the thread of the splash screen window does not use flannablethread:: create.
I think the reason why they don't use it is that they are relatively simple, functionally marginalized and don't want to rely on flannable. Or they were created earlier, and the content that flannable depends on has not been initialized.

3. Flannable and flannablethread

The flannable class is inherited according to its function, and has many subclasses. It is an abstract class in itself

class CORE_API FRunnable

	 * Initializes the runnable object.
	 * This method is called in the context of the thread object that aggregates this, not the
	 * thread that passes this runnable to a new thread.
	 * @return True if initialization was successful, false otherwise
	 * @see Run, Stop, Exit
	virtual bool Init()
		return true;

	 * Runs the runnable object.
	 * This is where all per object thread work is done. This is only called if the initialization was successful.
	 * @return The exit code of the runnable object
	 * @see Init, Stop, Exit
	virtual uint32 Run() = 0;

	 * Stops the runnable object.
	 * This is called if a thread is requested to terminate early.
	 * @see Init, Run, Exit
	virtual void Stop() { }

	 * Exits the runnable object.
	 * Called in the context of the aggregating thread to perform any cleanup.
	 * @see Init, Run, Stop
	virtual void Exit() { }

	 * Gets single thread interface pointer used for ticking this runnable when multi-threading is disabled.
	 * If the interface is not implemented, this runnable will not be ticked when FPlatformProcess::SupportsMultithreading() is false.
	 * @return Pointer to the single thread interface or nullptr if not implemented.
	virtual class FSingleThreadRunnable* GetSingleThreadInterface( )
		return nullptr;

	/** Virtual destructor */
	virtual ~FRunnable() { }

The flannablethread is inherited according to the platform. For example, its subclass is flannablethreadwin flannablethreadandroid flannablethreadunix.
It has a flannable * runnable member and needs to be given a flannable when it is created.
I think the flannablethread is equivalent to a "shell". According to the platform, a thread belonging to that platform will be created, and the flannable is the "core", which defines what this thread does.

4. Some information:

Official Wiki: multi threading: how to create threads in UE4 It's a practice. In summary, it inherits a flannable and uses flannablethread:: create to get it running.
Official Wiki: multi threading: task graph system It is a practice. In summary, it uses the TaskGraph system to accomplish the same thing in the previous article.
Detailed explanation of multithreading mechanism in Exploring in UE4 [principle analysis] - Zhihu It is an analysis of UE4 multithreading system.

Published 15 original articles, praised 0, visited 585
Private letter follow

Tags: Windows shell

Posted on Tue, 17 Mar 2020 04:13:42 -0700 by furtivefelon