美文网首页
23 UE5多线程 Runnable GraphTask(STL

23 UE5多线程 Runnable GraphTask(STL

作者: 游戏开发程序员 | 来源:发表于2024-04-17 17:48 被阅读0次

    线程

    • 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();
        }
    
    • FMyTask类定义
    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)

    总结

    • 最重要的还是任务图系统,
    • 三大全局线程也都是在任务图系统中调配的。
    • 合理使用已有线程池,异步操作可以直接用,最后才是考虑自己创建线程。

    相关文章

      网友评论

          本文标题:23 UE5多线程 Runnable GraphTask(STL

          本文链接:https://www.haomeiwen.com/subject/tughzdtx.html