Unreal Engine提供了多种并发机制,从简单的原子操作,到复杂的TaskGraph系统。线程及其同步机制是最基础的,其本身许多内容和C++标准线程库、Pthread等线程库并无二致。本文简单整理下,UE并发中最基础的内容:
- 线程/线程管理器的结构
- 线程的常见操作
- 三种同步机制:原子操作、临界区/读写锁、事件机制。
1.线程
1.1.结构
接口层:
- FRunnable - 可在任何一个线程中运行的对象,调用模式:Init() -> Run() -> Exit()。
- FRunnableThread - 平台对立的线程对象,创建时根据平台创建相应的子类。
- FThreadManager - 线程管理器,所有使用
FRunableThread::Create
创建的线程,都会添加都该全局管理器。 - FSingleThreadRunnable - 提供Tick接口,对于不支持多线程的系统,可以创建FFakeThread,然后通过主线程中调用FThreadManager::Tick来驱动FakeThread运行的Tick操作。
实现层:
- FRunnableThreadWin - Windows系统下的线程的实现
- FRunnableThreadPThread - pthread的封装,一般Unix/Linux/Mac多线程都是用pthread库实现。
- FFakeThread - 不支持多线程的系统,模拟一个“假”线程。
1.2.常用操作
创建线程:使用
FRunnableThread::Create
创建线程并运行
1 | class FSimpleThread : public FRunnable |
线程回调:在创建的线程中运行
Init、Run、Exit
。
1 | virtual bool Init() override |
结束线程:跳出
Run
操作,等待线程结束(RunnableThread->WaitForCompletion
)。
1 | // 等待线程结束(WaitForCompletion),线程的Join操作。 |
获取当前线程ID和名字
1 | uint32 CurrentThreadId = FPlatformTLS::GetCurrentThreadId(); |
遍历当前所有线程对象
1 | inline void DumpAllThreads(const char* Log) |
1.3.完整示例
https://github.com/david-pp/UESnippets/blob/main/SnippetAsync/Private/SimpleThread.h
1 | class FSimpleThread : public FRunnable |
2.同步机制
支持三种类型的同步操作,用于线程之间的同步操作:
- 原子操作
- 临界区/读写锁
- 事件
2.1.Atomic
FPlatformAtomics
平台独立的原子操作,编译器和系统级的原子操作封装。
原子操作:TAtomic -> std::atomic
TAtomic,是Unreal使用模板实现的原子操作封装,功能类似std::atomic
,随着C++标准对于并发支持的完善,官方建议使用std::atomic
。
1 | TAtomic<int> Counter; |
支持原子操作的计数器:FThreadSafeCounter/FThreadSafeCounter64/FThreadSafeBool
1 | FThreadSafeCounter Counter2; |
2.2.Locking
互斥锁/临界区:FCriticalSection/FScopeLock
FCriticalSection,Windows下使用临界区实现,Pthread用mutex实现。
1 | class ThreadSafeArray |
读写锁:FRWLock/FRWScopeLock
若读操作远高于写操作,建议使用读写锁。
1 | class ThreadSafeArray2 |
2.3.Event
FEvent,是可等待事件的接口,用来线程之间事件的等待和触发。
- 功能类似
std::condition_variable
- Windows系统下使用
CreateEvent
创建,pthread使用pthread_cond_create
来创建。 - 作为系统级的资源,为了降低创建和释放的消耗,创建时优先从EventPool中拿出来一个Event对象。
相关类及其用法:
FEvent - 可等待事件接口,支持ManualReset/AutoReset两种模式。
1 | inline void Test_Event() |
FEventRef - 构造时创建一个Event,析构时释放该事件。
1 | inline void Test_Event2() |
FScopeEvent - 构造时创建一个Event,析构时等待该事件,收到该事件,结束并释放事件资源。
1 | inline void Test_Event() |
3.参考源码
HAL(Hardware Abstract Layer),平台抽象层相关实现,跨平台线程、文件系统等操作。比如,线程相关操作位于FPlatformProcess::XXXX、本地线程缓存位于FPlatoformTLS::XXXX等待。
Engine/Source/Runtime/Core/Public/HAL/Runnable.h
Engine/Source/Runtime/Core/Public/HAL/RunnableThread.h
Engine/Source/Runtime/Core/Public/HAL/ThreadManager.h
Engine/Source/Runtime/Core/Public/HAL/Event.h