ue4-多线程使用

主线程异步或多线程异步的使用


在GameThread线程之外的其他线程中,不允许做一下事情

  • 不要 spawning / modifying / deleting UObjects / AActors
  • 不要使用定时器 TimerManager
  • 不要使用任何绘制接口,例如 DrawDebugLine

  • 如果想在主线程中异步处理(也就是分帧处理),可以使用一下接口(在 Async.h 中)

        AsyncTask(ENamedThreads::GameThread, [&]() {
            UE_LOG(LogMyTest, Warning, TEXT("--- UMyGameInstance::MyAsyncTask"));
            SpawnActor(3);
        });

多线程使用

新开线程线程的类

  • PrimeNumberWorker.h

    class UMyGameInstance;
    
    //~~~~~ Multi Threading ~~~
    class FPrimeNumberWorker : public FRunnable
    {
        static  FPrimeNumberWorker* Runnable; 
        FRunnableThread* Thread;
        TArray<uint32>* PrimeNumbers;
        UMyGameInstance* mGameIns;
        FThreadSafeCounter StopTaskCounter; //用于多线程间的判断交互,volatile int32 Counter;
        int32 FindNextPrimeNumber();
    
        FCriticalSection QueueCritical; //互斥锁
        FEvent* ThreadSuspendedEvent;   //线程悬挂和唤醒事件
    
    public:
        bool IsFinished();
        void Suspend();
        void Resume();
    
        //Constructor / Destructor
        FPrimeNumberWorker(TArray<uint32>& TheArray, UMyGameInstance* GameIns);
        virtual ~FPrimeNumberWorker();
    
        // Begin FRunnable interface.
        virtual bool Init();
        virtual uint32 Run();
        virtual void Stop();
        // End FRunnable interface
    
        /** Makes sure this thread has stopped properly */
        void EnsureCompletion();
        FCriticalSection* GetCriticalSection();
    
        static FPrimeNumberWorker* JoyInit(TArray<uint32>& TheArray, UMyGameInstance* GameIns);
        static FPrimeNumberWorker* Get();
        static void Shutdown();
        static bool IsThreadFinished();
    };
  • PrimeNumberWorker.cpp

    
    #include "MyTest.h"
    
    
    #include "PrimeNumberWorker.h"
    
    
    #include "../MyGameInstance.h"
    
    
    FPrimeNumberWorker* FPrimeNumberWorker::Runnable = nullptr;
    FPrimeNumberWorker::FPrimeNumberWorker(TArray<uint32>& TheArray, UMyGameInstance* GameIns)
        : mGameIns(GameIns)
        , StopTaskCounter(0)
    {
        ThreadSuspendedEvent = FPlatformProcess::GetSynchEventFromPool();
        PrimeNumbers = &TheArray;
        Thread = FRunnableThread::Create(this, TEXT("FPrimeNumberWorker"), 0, TPri_BelowNormal); //windows default = 8mb for thread, could specify more
    }
    
    FPrimeNumberWorker::~FPrimeNumberWorker()
    {
        delete Thread;
        Thread = nullptr;
        FPlatformProcess::ReturnSynchEventToPool(ThreadSuspendedEvent);
        ThreadSuspendedEvent = nullptr;
    }
    
    bool FPrimeNumberWorker::Init()
    {
        PrimeNumbers->Empty();
        return true;
    }
    
    uint32 FPrimeNumberWorker::Run()
    {
        //Initial wait before starting
        FPlatformProcess::Sleep(0.03);
        while (StopTaskCounter.GetValue() == 0 && !IsFinished())
        {
            FScopeLock* QueueLock = new FScopeLock(&QueueCritical); //锁住
            //***************************************
            //不要 spawning / modifying / deleting UObjects / AActors 等等之类的事
            //这里做多线程间共享信息的 modify,如:PrimeNumbers->Add
            //***************************************
            PrimeNumbers->Add(FindNextPrimeNumber());
            UE_LOG(LogMyTest, Warning, TEXT("--- FPrimeNumberWorker::Run, lock"));
            //Suspend();
            //prevent thread from using too many resources
            FPlatformProcess::Sleep(1.0f); //这里睡眠3秒是为了让GameThread中的UMyGameInstance::MyAsyncThread中的日志打不出来
            delete QueueLock;//解锁
            UE_LOG(LogMyTest, Warning, TEXT("--- FPrimeNumberWorker::Run, unlock"));
            //FPlatformProcess::Sleep(2.0f); //这里睡眠2秒是为了让GameThread中获取到 互斥锁QueueCritical 并锁住
        }
        Stop();
        return 0;
    }
    
    bool FPrimeNumberWorker::IsFinished()
    {
        return PrimeNumbers->Num() == 5;
    }
    
    void FPrimeNumberWorker::Suspend()
    {
        ThreadSuspendedEvent->Wait();
    }
    
    void FPrimeNumberWorker::Resume()
    {
        ThreadSuspendedEvent->Trigger();
    }
    
    void FPrimeNumberWorker::Stop()
    {
        StopTaskCounter.Increment();
    }
    
    FPrimeNumberWorker* FPrimeNumberWorker::JoyInit(TArray<uint32>& TheArray, UMyGameInstance* GameIns)
    {
        if (!Runnable && FPlatformProcess::SupportsMultithreading())
        {
            Runnable = new FPrimeNumberWorker(TheArray, GameIns);
        }
        bool isSupport = FPlatformProcess::SupportsMultithreading();
        FString msg = isSupport ? "SupportsMultithread" : "dont SupportsMultithreading";
        UE_LOG(LogMyTest, Warning, TEXT("--- FPrimeNumberWorker::JoyInit, msg:%s"), *msg);
        return Runnable;
    }
    
    FPrimeNumberWorker* FPrimeNumberWorker::Get()
    {
        return Runnable;
    }
    
    void FPrimeNumberWorker::EnsureCompletion()
    {
        Stop();
        Thread->WaitForCompletion();
    }
    
    FCriticalSection* FPrimeNumberWorker::GetCriticalSection()
    {
            return &QueueCritical;
    }
    
    void FPrimeNumberWorker::Shutdown()
    {
        if (Runnable)
        {
            Runnable->EnsureCompletion();
            delete Runnable;
            Runnable = nullptr;
        }
    }
    
    bool FPrimeNumberWorker::IsThreadFinished()
    {
        if (Runnable) return Runnable->IsFinished();
        return true;
    }
    
    int32 FPrimeNumberWorker::FindNextPrimeNumber()
    {
        int32 TestPrime = 123;
        return TestPrime;
    }
  • 再来个测试方法,写在GameInstanece的扩展类中(习惯了测试用 console命令调方法,不懂的看这里 ue4-控制台执行方法

    UFUNCTION(Exec)
        virtual void MyAsyncSuspend();
    UFUNCTION(Exec)
        virtual void MyAsyncResume();
    UFUNCTION(Exec)
        virtual void MyAsyncThread();
    
    void UMyGameInstance::MyAsyncSuspend()
    {
        FPrimeNumberWorker::Get()->Suspend();
    }
    
    void UMyGameInstance::MyAsyncResume()
    {
        FPrimeNumberWorker::Get()->Resume();
    }
    
    void UMyGameInstance::MyAsyncThread()
    {
        mNumVec.Empty();
        FPrimeNumberWorker::JoyInit(mNumVec, this);
        GetTimerManager().SetTimer(mTimer1, [&]()->void {
            FPrimeNumberWorker* pnw = FPrimeNumberWorker::Get();
            if (!pnw) return;
    
            FCriticalSection* cs = pnw->GetCriticalSection(); //获取FPrimeNumberWorker到中的互斥锁QueueCritical
            FScopeLock QueueLock(cs);//锁住,等作用域过后QueueLock自动析构解锁
            UE_LOG(LogMyTest, Warning, TEXT("--- UMyGameInstance::MyAsyncThread, mNumVec.Num=%d"), mNumVec.Num());
            if (pnw->IsThreadFinished())
                FPrimeNumberWorker::Shutdown();
        }, 1.0f, true);
    }
    1. 测试互斥。 上面的 FPrimeNumberWorker 里面的代码方法是有加锁的,在控制台输入指令 MyAsyncThread 可测试。注释掉 FScopeLock QueueLock(cs); 这一行代码就能看出没有互斥锁的区别。
    2. 测试线程的悬挂和唤醒。
      1. 取消注释 FPrimeNumberWorker 里面的 //Suspend();
      2. 注释掉 MyAsyncThread 测试方法中的的定时器 GetTimerManager(因为其他线程中在 未解锁 的 情况下 悬挂 的话,主线程获取不到这个锁会一直等待,也就是看起来像卡死了)
      3. 在控制台输入指令 MyAsyncThread,就可以 Run 方法的线程悬挂住该线程
      4. 在控制台输入指令 MyAsyncResume,就可以 唤醒之前调用 ThreadSuspendedEvent->Wait(); 的线程。

线程唤醒、悬挂

  • FEvent* ThreadSuspendedEvent;
  • 线程悬挂。A线程中调用 ThreadSuspendedEvent->Wait();,A线程就会悬挂主,必须由B线程调用 ThreadSuspendedEvent->Trigger(); 才能唤醒A线程。
  • 所以如果直接在主线程(GameThread)中调用 ThreadSuspendedEvent->Wait();,那就看起像卡死了。

编辑器模式下进行多线程开发注意事项

  • PIE模式下,与新开的线程交互正常;如果退出PIE模式,PIE中的实例对象都会被标记为坐等回收的对象 InValid,此时如果新开的线程还在Run中跑用到PIE中的实例对象,将造成 编辑器崩溃

  • 最好在你的游戏退出时 FPrimeNumberWorker ShutDown一下,方便下次测试。

    void UMyGameInstance::Shutdown()
    {
        FPrimeNumberWorker::Shutdown();
        Super::Shutdown();
    }

参考资料

  • 4
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
### 回答1: 在UE4,可以通过使用多线程来运行插件,以提高程序的性能和并行处理能力。 首先,插件在UE4是以模块的形式存在的,可以独立于游戏项目而存在。为了实现多线程运行插件,可以在插件的模块编写异步任务,利用多线程执行这些任务。 在UE4,我们可以使用FRunnable接口来创建自定义线程。首先,需要创建一个继承自FRunnable的类,并实现必要的接口函数,如Run和Stop。在Run函数,编写插件需要执行的任务代码。 在插件的初始化阶段,可以通过调用FRunnableThread::Create函数来创建一个新的线程,并指定我们刚刚定义的FRunnable类对象作为参数。然后,可以开始执行插件任务。 需要注意的是,在多线程编程,需要合理地处理线程之间的数据共享和同步问题。可以使用一些线程同步的机制,如互斥锁、信号量等,来避免多个线程同时操作共享数据导致的冲突。 另外,UE4还提供了一些已经封装好的多线程工具类,如FGraphEvent和FQueuedThread等,可以帮助我们更方便地实现多线程任务的管理和同步。 总结来说,UE4可以通过使用FRunnable接口和多线程工具类来实现插件的多线程运行。合理地设计和管理多线程任务,可以提高程序的性能和并行处理能力,并且确保线程之间的数据共享和同步的正确性。 ### 回答2: 在UE4,我们可以通过多线程来实现插件的运行。多线程是一种并发执行任务的方式,可以让程序同时执行多个任务,提高了程序的效率和性能。 在UE4,我们可以使用一些多线程的技术来实现插件的运行。比如,使用C++的std::thread来创建多个线程,并让每个线程在后台执行插件相关的任务。同时,我们还可以使用一些线程同步的机制,比如互斥锁、条件变量等来管理多个线程之间的资源访问和共享。 在插件的运行过程,可以将一些耗时的任务放在单独的线程执行,这样就不会阻塞主线程的执行。比如,可以将插件的某个算法或者处理逻辑放在一个独立的线程运行,这样主线程可以继续执行其他的任务,提高了程序的响应速度和用户体验。 除了使用多线程,还可以使用UE4提供的任务图谱系统来实现插件的并行执行。任务图谱系统可以将任务划分成多个小任务,并按照依赖关系进行调度,从而充分利用计算资源并提高任务执行的效率。 总结来说,UE4可以通过多线程技术来实现插件的运行,可以提高程序的效率和性能,同时还可以使用任务图谱系统来实现任务的并行执行。这些技术可以充分发挥计算资源的利用率,提高插件的运行效果和用户体验。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蝶泳奈何桥.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值