日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

UE4异步编程专题 - 多线程

發布時間:2023/12/10 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 UE4异步编程专题 - 多线程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

專題的第二篇,我們聊聊UE4中的多線程的基礎設施。UE4中最基礎的模型就是FRunnable和FRunnableThread,FRunnable抽象出一個可以執行在線程上的對象,而FRunnableThread是平臺無關的線程對象的抽象。后面的篇幅會詳細討論這些基礎設施。

1. FRunnable

UE4為我們抽象FRunnable的概念,讓我們指定在線程上運行的一段邏輯過程。該過程通常是一個較為耗時的操作,例如文件IO;或者是常態為空閑等待(Idle)的循環,隨時等待新執行命令到來。

FRunnable為我們提供了四個重要的接口:

class CORE_API FRunnable { public:// ....virtual bool Init();virtual uint32 Run() = 0;virtual void Stop() {}virtual void Exit() {} };
  • Init是對FRunnable對象的初始化,它是由FRunnableThread在創建線程對象后,進入線程函數的時候立即被FRunnableThread調用的函數,并不能由用戶自己調用;
  • Run是Runnable過程的入口,同樣也是有FRunnableThread在Init成功后調用;
  • Exit是Run正常退出后,由FRunnableThread調用,進行對FRunnable對象的清理工作;
  • Stop是給用戶使用的接口,當我們覺得必要時停止FRunnable.
  • 例如一個空閑等待的FRunnable的實現:

    class MyRunnable : public FRunnable { public:MyRunnable(): RunningFlag(false), WorkEvent(FPlatformProcess::GetSynchEventFromPool()){}~MyRunnable(){FPlatformProcess::ReturnSynchEventToPool(WorkEvent);WorkEvent = nullptr;}bool Init() override{// ..if(!WorkEvent)return false;RunningFlag.Store(true);}void Run() override{while(RunningFlag.Load()){WorkEvent->Wait(MAX_uint32);if(!RunningFlag.Load())break;// ...}}void Stop() override{if(RunningFlag.Exchange(false))WorkEvent->Trigger();}void Exit() overrdie{// ...RunningFlag.Store(false);}void Notify(){WorkEvent->Trigger();}private:TAtomic<bool> RunningFlag;FEvent* WorkEvent;// ... };

    原子變量RunningFlag作為Runnable對象的運行狀態的標記,所以Run函數的主體就是在RunningFlag為ture的情況無限循環。WorkEvent是其他線程上執行的任務與MyRunnable交互的事件對象,通過Notify接口,可以喚醒它繼續執行。MyRunnable從Wait中醒來時,還會檢查一次RunningFlag,有可能是喚醒它的是Stop接口發出的事件。而Stop的實現,會判斷一下標識是否Runnable已經退出,而不用再次發出事件了。

    2. FRunnableThread

    FRunnable需要依附與一個FRunnableThread對象,才能被執行。例如,我們如果要執行第一節的空閑等待的Runnable:

    auto* my_runnable = new MyRunnable{};auto* runnable_thread = FRunnableThread::Create(my_runnable, "IdleWait");

    FRunnableThread是平臺無關的線程對象的抽象,它驅動著FRunnable的初始化,執行和清理,并提供了管理線程對象生命周期,線程局部存儲,親緣性和優先級等接口。

    class FRunnableThread {// ....// Tls 索引static uint32 RunnableTlsSlot;public:// 獲取Tls索引static uint32 GetTlsSlot();// 平臺無關的創建線程對象接口static FRunnableThread* Create(class FRunnable* InRunnable,const TCHAR* ThreadName,uint32 InStackSize,EThreadPriority InThreadPri,uint64 InThreadAffinityMask);public:// 設置線程優先級virtual void SetThreadPriority( EThreadPriority NewPriority ) = 0;// 掛起線程virtual void Suspend( bool bShouldPause = true ) = 0;// 殺死線程virtual bool Kill( bool bShouldWait = true ) = 0;// 同步等待線程退出virtual void WaitForCompletion() = 0;protected:// 平臺相關的線程對象初始化過程virtual bool CreateInternal(FRunnable* InRunnable, const TCHAR* InThreadName,uint32 InStackSize,EThreadPriority InThreadPri, uint64 InThreadAffinityMask) = 0; };

    UE4已經實現了各個平臺的線程對象。Win平臺使用的是系統Windows的Thread API. 而其他平臺是基于pthread,不同平臺實現上略有不同。通過編譯選項包含平臺相關的頭文件,并通過FPlatformProcess類型的定義來選擇相應平臺的實現。參見FRunnableThread::Create函數:

    FRunnableThread* FRunnableThread::Create(class FRunnable* InRunnable, const TCHAR* ThreadName,uint32 InStackSize,EThreadPriority InThreadPri, uint64 InThreadAffinityMask) {// ....NewThread = FPlatformProcess::CreateRunnableThread();if (NewThread){if (NewThread->CreateInternal(...))// .....}// .... }

    線程對象的創建,需要指定一個FRunnable對象的實例。

    FPlatformProcess::CreateRunnableThread就是簡單地new一個平臺相關的線程對象,而真正的初始化時在FRunnableThread::CreateInternal當中完成的。線程平臺相關的API差異很大,UE4的封裝盡可能地讓各個平臺的實現略有不同。

    系統API創建的線程對象,都以_ThreadProc作為入口函數。接下來是一系列的平臺相關的初始化工作,例如設置棧的大小,TLS的索引,親緣性掩碼,獲取平臺相關的線程ID等。之后,就會進入上一節我們提及的FRunnable的初始化流程中了。一個線程創建成功的時序圖如下:

    Win平臺的實現中,由于API的歷史原因需要_ThreadProc的調用約定是STDCALL. 因此Win平臺下的_ThreadProc函數,是一個轉發函數,轉發給了另外一個CDECL調用約定的函數FRunnableThreadWin::GuardedRun.

    3. Runnable or Callable

    UE4的多線程模型是Runnable和Thread,但是有不少C++庫,如標準庫,是Callable and Thread. 如果使用標準庫的std::thread:

    int main(void) {std::thread t{ [](){ std::cout << "Hello Thread." } };t.join();return 0; }

    暫時忽略標準庫thread簡陋的設施,Callable和Runnable這兩個模型是可以等價的,也就是他們可以相互表達。

    例如我們可以用UE4的設施,實現類似std::thread的FThread(UE4已經實現了一個):

    class FThread final : public FRunnable { public:template <typename Func, typename ... Args>explicit FThread(Func&& f, Args&& ... args): Callable(create_callable(std::forward<Func>(f), std::forward<Args>(args)...)), Thread(FRunnableThread::Create(this, "whatever")){if(!Thread)throw std::runtime_error{ "Failed to create thread!" };}void join(){Thread->WaitForCompletion();}virtual uint32 Run() override{Callable();return 0;}private:template <typename Func, typename ... Args>static auto create_callable(Func&& f, Args&& ... args) noexcept{// 為了簡單起見用了20的特性,如果是17標準以下的話,用tuple也能辦到。// Eat return typereturn [func = std::forward<Func>(f), ... args = std::forward<Args>(args)](){std::invoke(func, std::forward<Args>(args...));};}private:TFunction<void()> Callable;FRunnableThread* Thread; };

    我們還可以用std::thread和一些封裝,來實現一個的RunnableThread. 下面是一個簡單的實現:

    class RunnableThread { public:explicit RunnableThread(FRunnable* runnable): runnable_(runnable), inited_(false), init_result_(false), thread_(&RunnableThread::Run, this){std::unique_lock<std::mutex> lock{ mutex_ };cv_.wait(lock, [this](){ return inited_; });}protected:void Run(){auto result = runnable_->Init();{std::unique_lock<std::mutex> lock{ mutex_ };inited_ = true;init_result_ = result;}cv_.notify_one();if(result){runnable_->Run();runnable_->Exit();}}private:FRunnable* runnable_;bool inited_;bool init_result_;std::thread thread_;std::mutex mutex_;std::condition_variable cv_; };

    雖然筆者不喜歡面向對象的設計(OOD),但UE4的FRunnable和FRunnaableThread實現得確實挺不錯。沒有很重的框架束縛,并且FRunnable也有著跟callable一樣的表達能力,并且FRunnableThread封裝了各個平臺線程庫幾乎所有的功能特性。總體上來說,比標準庫的thread設施更齊全。

    4. 小結

    UE4中的多線程模型用一句話概括為: A FRunnable runs on a FRunnableThread.

    FRunnable是邏輯上的可執行對象的概念的抽象。對于一個具體的可執行對象的實現,用戶需要實現Init和Exit接口,對Runnable需要的系統資源進行申請和釋放;用戶需要實現Run來控制Runnable的執行流程,并在需要的情況下實現Stop接口,來控制Runnable的退出。

    FRunnableThread是UE4提供的平臺無關的線程對象的抽象,并提供了控制線程對象生命周期和狀態的接口。UE4實現了常見所有平臺的線程對象,實現細節對用戶透明。

    除此之外,本文還討論了Runnable與Callable兩種模型,并且它們具有相同的表達能力。

    這個系列的下一篇,將會討論FQueuedThreadPool. 它是由FRunnable及FRunnableThread組合實現的,用于執行任務隊列的線程池。

    總結

    以上是生活随笔為你收集整理的UE4异步编程专题 - 多线程的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。