Bootstrap

虚幻引擎编程基础(二)

虚幻引擎编程基础(二)

一、前言

虚幻引擎编程基础(一) 中整理一些基础的数据结构用法。

本文主要会继续简单地整理一些相关的基础内容,如多线程,垃圾回收,以及一些常用的代码块。

以下是笔者的一些笔记。如有错误,还请见谅。

二、多线程

线程是操作系统能够进行运行调度的最小单位。

一个进程中可以并发多个线程,每条线程并行执行不同的任务。

游戏引擎中,多线程用的一点也不少,比如渲染模块、物理模块、网络通信、音频系统、IO等。

UE4使用多线程的方式非常丰富,可以分为下面三类:

  • 标准多线程实现FRunnable;
  • 使用线程池的AsyncTask;
  • TaskGraph;

2.1 FRunnable & FRunnableThread

UE4是跨平台的引擎,对各个平台线程实现进行了封装,抽象出了 FRunnable,类似与Java中的多线程方式。

主要需要认识的类有:

FRunnable
FRunnableThread

其中:

FRunnable是需要继承实现的线程执行体。其接口如下:

class CORE_API FRunnable
{
public:

	/**
	 * 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() { }
};

FRunnableThreadc,才是真正负责创建的多线程,它持有FRunnable的实例。

  • 通过FRunnableThread::Create创建相应平台的线程
FRunnableThread* FRunnableThread::Create(
	class FRunnable* InRunnable, 
	const TCHAR* ThreadName,
	uint32 InStackSize,
	EThreadPriority InThreadPri, 
	uint64 InThreadAffinityMask,
	EThreadCreateFlags InCreateFlags)
{
	bool bCreateRealThread = FPlatformProcess::SupportsMultithreading();

	FRunnableThread* NewThread = nullptr;

	if (bCreateRealThread)
	{
		check(InRunnable);
		// Create a new thread object
		NewThread = FPlatformProcess::CreateRunnableThread();
	}
	else if (InRunnable->GetSingleThreadInterface())
	{
		// Create a fake thread when multithreading is disabled.
		NewThread = new FFakeThread();
	}

	if (NewThread)
	{
		SetupCreatedThread(NewThread, InRunnable, ThreadName, InStackSize, InThreadPri, InThreadAffinityMask, InCreateFlags);
	}

	return NewThread;
}

例如Windows平台:

  1. FRunnableThread::SetupCreatedThread函数中,会调用NewThread->CreateInternal,从而调用FRunnableThreadWin的CreateInternal,进行Windows的线程创建;
// Create the new thread
{
    LLM_SCOPE(ELLMTag::ThreadStack);
    LLM_PLATFORM_SCOPE(ELLMTag::ThreadStackPlatform);
    // add in the thread size, since it's allocated in a black box we can't track
    LLM(FLowLevelMemTracker::Get().OnLowLevelAlloc(ELLMTracker::Default, nullptr, InStackSize));
    LLM(FLowLevelMemTracker::Get().OnLowLevelAlloc(ELLMTracker::Platform, nullptr, InStackSize));

    // Create the thread as suspended, so we can ensure ThreadId is initialized and the thread manager knows about the thread before it runs.
    Thread = CreateThread(NULL, InStackSize, _ThreadProc, this, STACK_SIZE_PARAM_IS_A_RESERVATION | CREATE_SUSPENDED, (::DWORD *)&ThreadID);
}
  1. 在传入的_ThreadProc函数,会调用GuardedRun方法;
/**
	 * The thread entry point. Simply forwards the call on to the right
	 * thread main function
	 */
static ::DWORD STDCALL _ThreadProc( LPVOID pThis )
{
    check(pThis);
    auto* ThisThread = (FRunnableThreadWin*)pThis;
    FThreadManager::Get().AddThread(ThisThread->GetThreadID(), ThisThread);
    return ThisThread->GuardedRun();
}
  1. GuardedRun方法中调用Run,从而运行FRunnable::Run(线性执行体)。

uint32 FRunnableThreadWin::Run()
{
	// Assume we'll fail init
	uint32 ExitCode = 1;
	check(Runnable);

	// Initialize the runnable object
	if (Runnable->Init() == true)
	{
		// Initialization has completed, release the sync event
		ThreadInitSyncEvent->Trigger();

		// Setup TLS for this thread, used by FTlsAutoCleanup objects.
		SetTls();

		// Now run the task that needs to be done
		ExitCode = Runnable->Run();
		// Allow any allocated resources to be cleaned up
		Runnable->Exit();

#if STATS
		FThreadStats::Shutdown();
#endif
		FreeTls();
	}
	else
	{
		// Initialization has failed, release the sync event
		ThreadInitSyncEvent->Trigger();
	}

	return ExitCode;
}

以上就是Unreal自带最基础的多线程的实现方式。

渲染线程的创建就是使用的这种方法。

LauchEngineLoop.cppFEngineLoop::PreInitPreStartupScreen函数中会调用StartRenderingThread函数:

// Turn on the threaded rendering flag.
GIsThreadedRendering = true;

// Create the rendering thread.
// 创建渲染线程
GRenderingThreadRunnable = new FRenderingThread();

Trace::ThreadGroupBegin(TEXT("Render"));
PRAGMA_DISABLE_DEPRECATION_WARNINGS
    GRenderingThread = 
    PRAGMA_ENABLE_DEPRECATION_WARNINGS
    FRunnableThread::Create(GRenderingThreadRunnable, 
                            *BuildRenderingThreadName(ThreadCount), 0, 
                            FPlatformAffinity::GetRenderingThreadPriority(), 
                            FPla
;