线程
- join 阻塞,等待线程执行完毕
- detach 分离当前持有的线程,继续执行
// 打印3个输入的数字
int ThreadFuncPrint(int8 id)
{
for (int i = 0; i < 3; i++)
{
UE_LOG(TestLog, Warning, TEXT("dosomething: %d"), id);
}
return 1;
}
// STL thread
if (0)
{
// 最简单的线程
thread t([]()
{
UE_LOG(TestLog, Warning, TEXT("thread short"));
}
);
// 方法1 等待线程执行完成,阻塞调用该方法的线程
t.join();
// 2 将线程分离,使得线程的执行与主线程分离,主线程不再等待子线程的结束。
// 如果主线程提前结束,可能会导致子线程无法完整执行。
//t.detach();
// thread_local
std::thread t1(ThreadFuncPrint, 1);
std::thread t2(ThreadFuncPrint, 2);
t1.join();
t2.join();
}
UE FRunnable类
- UE5推荐的处理并行任务的类
- init run exit stop
- 使用流程是先Init,再Run,最后Exit。
- Exit函数需要执行清理操作。
- Stop:提前停止,终止任务的执行。
FRunnableThread类
- 和可运行对象FRunnable绑定的线程
- Create, Suspend, Kill, Tick等
- WaitForCompletion: 暂停调用者 caller,直到当前线程执行完后再继续执行。
- 成员变量:线程名、线程ID、线程优先级、可运行对象的指针等
- FRunnableThreadWin类 继承:FRunnableThread类
// UE RunnableThread
if (0)
{
// 创建FRunnable对象
FMyRunnable* MyRunnable1 = new FMyRunnable(1);
FMyRunnable* MyRunnable2 = new FMyRunnable(2);
// 创建线程对象,并关联FRunnable对象
FRunnableThread* MyThread1 = FRunnableThread::Create(MyRunnable1, TEXT("MyThread1"));
FRunnableThread* MyThread2 = FRunnableThread::Create(MyRunnable2, TEXT("MyThread2"));
MyThread1->WaitForCompletion();
MyThread2->WaitForCompletion();
delete(MyThread1);
delete(MyRunnable1);
delete(MyThread2);
delete(MyRunnable2);
}
// 自定义的实现了FRunnable接口的类
class FMyRunnable : public FRunnable
{
public:
FMyRunnable(int8 _id) : ID(_id) {}
~FMyRunnable()
{
UE_LOG(LogTemp, Warning, TEXT("~FMyRunnable : %d"), ID);
}
virtual bool Init() override
{
UE_LOG(LogTemp, Warning, TEXT("FMyRunnable Init: %d"), ID);
return true;
}
virtual uint32 Run() override
{
for (int i = 0; i < 3; i++)
{
UE_LOG(LogTemp, Warning, TEXT("FMyRunnable Run: %d"), ID);
FPlatformProcess::Sleep(1.0f);
}
return 0;
}
virtual void Stop() override
{
UE_LOG(LogTemp, Warning, TEXT("FMyRunnable Stop: %d"), ID);
}
virtual void Exit() override
{
UE_LOG(LogTemp, Warning, TEXT("FMyRunnable Exit: %d"), ID);
}
private:
int8 ID;
};
FThreadManager类
- 管理FRunnable FRunnableThread类
- 创建、启动、停止和管理多个线程,实现任务的并行执行。
- Get, AddThread, RemoveThread, ForEachThread等主要函数
- Tick: 如果设备环境不支持多线程,模拟对非real的Thread->Tick()
FScopeLock类
- 处理作用域级别的锁定。
- 有Unlock函数, 构造既lock()
- 成员变量SynchObject:临界区指针。
- FScopeLock Lock(&AsyncTaskLock);
FCriticalSection AsyncTaskLock;
// 定义一个异步任务
void MyAsyncTask()
{
// 使用lock 确保每个线程函数1~5打印完毕, 不插队
FScopeLock Lock(&AsyncTaskLock);
// 模拟耗时操作,将数组中的每个元素都乘以2
for (int32 Number : {1,2,3,4,5})
{
UE_LOG(TestLog, Warning, TEXT("MyAsyncTask print : %d fromTID: %d"),
Number, FPlatformTLS::GetCurrentThreadId());
FPlatformProcess::Sleep(0.5f); // 模拟耗时操作
}
// 异步任务完成后,可以进行一些回调操作
UE_LOG(LogTemp, Warning, TEXT("MyAsyncTask completed!"));
}
TFuture类 类似STL
- 一个会在未来某个点返回的值
- Get函数:立即执行并获取结果
// async and future 方便的使用异步方法
if (0)
{
// 异步启动一个分离线程在后台,不明确调用时间
future<int> result1 = async(ThreadFuncPrint, 1);
// 明确以异步方式启动目标函数,如果无法启动.抛出异常.
future<int> result2 = async(launch::async, ThreadFuncPrint, 2);
// 强制延缓调用,必须等到get启动
future<int> result3 = async(launch::deferred, ThreadFuncPrint, 3);
result1.wait(); // wait强制执行后台异步线程
result3.get(); // get强制执行并且拿到返回值
// get在单线程上也可以保证.get只能调用一次.
int result = result1.get() + ThreadFuncPrint(4);
// 检查有效性 get之后就无效了
check(!result1.valid())
}
// UE Async
if (0)
{
// 调用Async函数来执行异步任务
TFuture<int> Result = Async(EAsyncExecution::Thread, []() { return 123; });
UE_LOG(TestLog, Warning, TEXT("Async Result : %d"), Result.Get());
check(Result.IsValid());
TFuture<void> Result2 = Async(EAsyncExecution::Thread, MyAsyncTask);
TFuture<void> Result3 = Async(EAsyncExecution::Thread, MyAsyncTask);
TFuture<void> Result4 = Async(EAsyncExecution::Thread, MyAsyncTask);
Result2.Get(); // 等待执行完毕
UE_LOG(TestLog, Warning, TEXT("Async call MyAsyncTask"));
check(Result2.IsValid());
}
TPromise类
- 允许你在一个线程中设置结果,并在另一个线程中获取结果。
- GetFuture:获取一个future对象
- SetValue: 设置promise的结果
- Wait - 等结果出来
- WaitFor - 等结果出来 || 在一段时间内
- WaitUntil - 等结果出来 || 到某年某月某一天
- FutureRetrieved 是否获取过
// UE TPromise
if (0)
{
// 承诺给一个bool结果,附加回调
TPromise<bool> Promise([]()
{
UE_LOG(LogTemp, Display, TEXT("the promise is set "));
});
// 返回一个尚未兑现的未来
TFuture<bool> Future = Promise.GetFuture();
// AnyThread中执行
FFunctionGraphTask::CreateAndDispatchWhenReady([&Promise]()
{
// 模拟执行一段任务
FPlatformProcess::Sleep(3);
UE_LOG(LogTemp, Display, TEXT("do the promise"));
// 设置结果
Promise.SetValue(true);
UE_LOG(LogTemp, Display, TEXT("Promise.SetValue(true)"));
});
UE_LOG(LogTemp, Display, TEXT("waiting for the promise..."));
// 等待实现承诺
// - Wait - 等结果出来
// - WaitFor - 等结果出来 || 在一段时间内
// - WaitUntil - 等结果出来 || 到某年某月某一天
Future.Wait();
//Future.WaitFor(FTimespan::FromSeconds(5));
// 注意: 不保证Promise.SetValue(true)之后的代码执行
if (Future.IsReady())
{
UE_LOG(LogTemp, Display, TEXT("promise future is %d"), Future.Get());
}
else // 结果未设置
{
UE_LOG(LogTemp, Display, TEXT("promise Future is not ready"));
}
}
TGraphTask类
- 基于任务(task-based)的并行编程类
- 提供设置和处理前置条件和后续任务的功能。
- 在创建任务时,可以指定一个或多个前置任务,组成一个 Graph
- 可以指定任务在哪个线程中执行;
// UE TaskGraph
if (1)
{
// 连续函数调用 延迟构造设计
// 1 首先创建一个任务实例 CreateTask()
// 2 对任务实例调用 ConstructAndDispatchWhenReady
FGraphEventRef evnetRef1 = TGraphTask<FMyTask>::CreateTask().ConstructAndDispatchWhenReady(1, 5);
// 前置任务集合
FGraphEventArray RootTasks = { evnetRef1 };
// 无牵无挂
// FGraphEventRef evnetRef2 = TGraphTask<FMyTask>::CreateTask().ConstructAndDispatchWhenReady(6, 10);
// 等待RootTasks里面的任务完成后再执行
FGraphEventRef evnetRef2 = TGraphTask<FMyTask>::CreateTask(&RootTasks, ENamedThreads::AnyThread).ConstructAndDispatchWhenReady(6, 10);
// 等待任务执行完成
evnetRef1->Wait();
// 等待任务执行完成
evnetRef2->Wait();
}
class FMyTask : public FNonAbandonableTask
{
public:
FMyTask(int32 InStartValue, int32 InEndValue)
: StartValue(InStartValue), EndValue(InEndValue){ }
// 当前Task的记录类型
static TStatId GetStatId()
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FMyTask, STATGROUP_TaskGraphTasks);
}
// 定义任务在哪个线程上执行,这里使用线程池
static ENamedThreads::Type GetDesiredThread()
{
return ENamedThreads::AnyThread;
}
// 是否被其他任务依赖 TrackSubsequents:依赖,FireAndForget:不依赖
static ESubsequentsMode::Type GetSubsequentsMode()
{
return ESubsequentsMode::TrackSubsequents;
}
// 任务的执行函数
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{
for (int32 i = StartValue; i <= EndValue; i++)
{
UE_LOG(LogTemp, Warning, TEXT("FMyTask DoTask value: %d"), i);
FPlatformProcess::Sleep(1);
}
}
private:
int32 StartValue;
int32 EndValue;
};
UE 重要线程
- GameThread:游戏线程,启动的主线程。
- GGameThreadId = FPlatformTLS::GetCurrentThreadId();
- RenderThread:GRenderingThread 渲染线程(任务图连接到渲染线程,处理线程任务)
- RHIThread:GRHIThread_InternalUseOnly
- (Render Hardware Interface Thread)是一个用于处理渲染相关任务的线程。
- 线程池(GThreadPool,GIOThreadPool,GBackgroundPriorityThreadPool,GLargeThreadPool)
总结
- 最重要的还是任务图系统,
- 三大全局线程也都是在任务图系统中调配的。
- 合理使用已有线程池,异步操作可以直接用,最后才是考虑自己创建线程。
网友评论