<cite id="ffb66"></cite><cite id="ffb66"><track id="ffb66"></track></cite>
      <legend id="ffb66"><li id="ffb66"></li></legend>
      色婷婷久,激情色播,久久久无码专区,亚洲中文字幕av,国产成人A片,av无码免费,精品久久国产,99视频精品3
      網(wǎng)易首頁 > 網(wǎng)易號(hào) > 正文 申請(qǐng)入駐

      UE5多線程|TaskGraph

      0
      分享至


      【USparkle專欄】如果你深懷絕技,愛“搞點(diǎn)研究”,樂于分享也博采眾長(zhǎng),我們期待你的加入,讓智慧的火花碰撞交織,讓知識(shí)的傳遞生生不息!

      這是侑虎科技第1932篇文章,感謝作者南京周潤(rùn)發(fā)供稿。歡迎轉(zhuǎn)發(fā)分享,未經(jīng)作者授權(quán)請(qǐng)勿轉(zhuǎn)載。如果您有任何獨(dú)到的見解或者發(fā)現(xiàn)也歡迎聯(lián)系我們,一起探討。(QQ群:793972859)

      作者主頁:

      https://www.zhihu.com/people/xu-chen-71-65

      TaskGraph是線程池的進(jìn)階,能讓任務(wù)之間產(chǎn)生依賴,上層可以方便地指定這種依賴。各任務(wù)的依賴關(guān)系就形成了“圖”。

      除了線程池,TaskGraph還可以管理GrameThread、RenderThread等獨(dú)立線程的調(diào)度,是UE中最復(fù)雜,功能最全面的多線程調(diào)度框架了。

      典型場(chǎng)景

      UE的多線程GC是TaskGraph的一個(gè)典型場(chǎng)景,需要把一個(gè)大的Array分割成若干小的Array,然后分到多個(gè)線程處理,GameThread需要等這些線程都處理完了,再執(zhí)行以后的任務(wù)。代碼如下:


      注意最后的ParallelFor,把多線程處理封裝成并行For行為,分發(fā)到多個(gè)線程,然后等待多線程執(zhí)行結(jié)束。

      如果用普通線程實(shí)現(xiàn)這個(gè)功能,需要手動(dòng)用FEvent實(shí)現(xiàn)等待,要寫一些特化代碼。

      一、使用TaskGraph

      1. Gamethread Tick

      最常見的GameThread World Tick,就是由TaskGraph驅(qū)動(dòng)的,因?yàn)镚ameThread也由TaskGraph管理,我們寫的Actor::Tick,Component::Tick都在這里執(zhí)行。Tick函數(shù)本身可以包裝到TGraphTask里,然后用WaitUntilTasksComplete函數(shù)執(zhí)行所有Task。


      2. Async函數(shù)

      Async函數(shù)可以指定EAsyncExecution::TaskGraph,讓任務(wù)在TaskGraph線程池中執(zhí)行。還能指定EAsyncExecution::TaskGraphMainThread,讓一些短時(shí)間任務(wù)在主線程執(zhí)行。


      3. WaitUntilTasksComplete

      如果需要發(fā)出一些異步任務(wù),然后等待執(zhí)行結(jié)束,可以手動(dòng)構(gòu)造FGraphEventArray,然后調(diào)用WaitUntilTasksComplete等待執(zhí)行完畢,這里能體現(xiàn)TaskGraph的調(diào)度。


      二、TaskGraph線程池

      TaskGraph包含了線程池功能,不妨首先看線程池部分是如何實(shí)現(xiàn)的,這也比較好切入。類似FQueuedThreadPoolBase結(jié)構(gòu),TaskGraph的線程池有FTaskGraphInterface、FScheduler、FThread、TGraphTask和TAsyncGraphTask。

      1. FTaskGraphInterface

      FTaskGraphInterface是TaskGraph的管理類,是個(gè)單例,本身也是Interface,一些重要功能由子類實(shí)現(xiàn)。

      接口

      • Startup:初始化TaskGraph。

      • Shutdown:關(guān)閉TaskGraph。

      • AttachToThread:把一個(gè)獨(dú)立線程添加到TaskGraph中,比如GameThread和RenderThread。

      • WaitUntilTasksComplete:讓一些線程運(yùn)行若干任務(wù),并在當(dāng)前線程等待這些任務(wù)都執(zhí)行完。

      • TriggerEventWhenTasksComplete:當(dāng)若干任務(wù)執(zhí)行完,觸發(fā)一個(gè)Fevent。

      • ProcessThreadUntilIdle:讓一個(gè)NameThread一直處理自己的TaskQueue,直到執(zhí)行完所有Task。

      子類

      FTaskGraphCompatibilityImplementation

      UE5的新TaskGraph子類,實(shí)現(xiàn)了TaskGraph的核心功能,不包含任務(wù)依賴功能,任務(wù)依賴由task實(shí)現(xiàn)。

      成員

      • uint32 PerThreadIDTLSSlot:TaskGraph用FWorkerThread結(jié)構(gòu)體管理線程,每個(gè)線程在自己的TLS變量中存儲(chǔ)指向FWorkerThread結(jié)構(gòu)的指針。

      • Int32 NumNamedThreads:Named線程數(shù)量。

      • Int32 NumWorkerThreads:Worker線程數(shù)量。

      • Int32 NumBackgroundWorkers:BackgroudWorker數(shù)量。

      • Int32 NumForegroundWorkers:ForegroundWorker數(shù)量。

      • TArray NamedThreads:管理了所有NamedThread。

      FTaskGraphImplementation:舊TaskGraph子類實(shí)現(xiàn),不看了。

      2. FScheduler

      FScheduler用于創(chuàng)建、管理Workder線程,以及把Task分派給Worker線程。

      成員

      • TArray > WorkerThreads:工作線程。

      • TAlignedArray WorkerLocalQueues:WorkerThread對(duì)應(yīng)的Task。

      • TAlignedArray WorkerEvents:WorkerThread對(duì)應(yīng)的Event。

      • EThreadPriority WorkerPriority:工作線程優(yōu)先級(jí)。

      • EThreadPriority BackgroundPriority:Background WorkerThread優(yōu)先級(jí)。

      • FSchedulerTls::FQueueRegistry QueueRegistry:全局任務(wù)隊(duì)列。

      方法

      • StartWorkers:創(chuàng)建WorkerThreads和Event等。

      • StopWorkers:執(zhí)行完所有Task,然后銷毀WorkerThreads。

      • TryLaunch:在WorkerThreads上執(zhí)行Task。

      • WakeUpWorker:通過Event Trigger喚醒WorkerThreads。

      3. FThread

      TaskGraph創(chuàng)建的WorkerThread,使用FThread來管理,它是操作系統(tǒng)中一個(gè)線程的表示,封裝了一個(gè)FThreadImpl。

      方法

      Join:最主要的方法,等待線程執(zhí)行完畢。

      成員

      TSharedPtr Impl:實(shí)際的Frunnable。

      4. FThreadImpl

      FThread的具體實(shí)現(xiàn),繼承自Frunnable。

      方法

      Run:調(diào)用了成員ThreadFunction。

      成員

      • TUniqueFunction ThreadFunction:線程要執(zhí)行的函數(shù),就是WorkerMain。

      • TUniquePtr RunnableThread:對(duì)應(yīng)的FRunnableThread對(duì)象。

      5. TGraphTask

      TaskGraph系統(tǒng)中管理的Task,不直接調(diào)用用戶提供的Task函數(shù),而是把函數(shù)封裝成一個(gè)user defined task,存儲(chǔ)在其中。

      成員

      • TAlignedBytes TaskStorage:存儲(chǔ)的user defined task,類型由模板指定。

      • FGraphEventRef Subsequents:存儲(chǔ)哪些GraphTask以我們?yōu)榍爸谩?/p>

      方法

      • CreateTask:創(chuàng)建一個(gè)新GraphTask。

      • ExecuteTask:執(zhí)行Task。

      • SetupPrereqs:設(shè)置Task前置。

      6. TAsyncGraphTask

      屬于user defined task,是UE為實(shí)現(xiàn)Async函數(shù)而創(chuàng)建的類。

      成員

      • TUniqueFunction Function:用戶提供的Task方法。

      • LowLevelTasks::FTask TaskHandle:FSchedule中對(duì)應(yīng)的FTask對(duì)象。

      方法

      DoTask:執(zhí)行Function。

      7. FTask

      Scheduler中使用的最底層任務(wù)對(duì)象。

      成員

      • FTaskDelegate Runnable:封裝的Task函數(shù)對(duì)象。

      • FPackedDataAtomic PackedData:Priority,DebugName等信息。

      方法

      ExecuteTask:執(zhí)行Task。

      借用其他博主畫的類圖,這張類圖畫的很好,但需要把其中的FTaskGraphImplementation類換成FTaskGraphCompatibilityImplementation:


      三、初始化Worker線程

      在PreInitPreStartupScreen函數(shù)中,會(huì)調(diào)用FTaskGraphInterface::Startup函數(shù)初始化TaskGraph,然后調(diào)用到Fscheduler::StartWorkers創(chuàng)建WorkerThreads。 參數(shù)NumberOfWorkerThreadsToSpawn與CPU核數(shù)有關(guān),Windows平臺(tái)為總核數(shù)減2,估計(jì)一個(gè)留給GameThread,一個(gè)留給RenderThread。




      WorkerThreads分為ForegroundWorker和BackgroundWorker,線程優(yōu)先級(jí)不一樣,分別是TPri_SlightlyBelowNormal和TPri_BelowNormal,F(xiàn)oregroundWorker默認(rèn)只有兩個(gè)。最終的創(chuàng)建WorkerThreads代碼如下:


      對(duì)于每個(gè)WorkerThread,要?jiǎng)?chuàng)建三樣?xùn)|西:

      • 首先創(chuàng)建一個(gè)屬于該WorkerThread的FSleepEvent,內(nèi)部包含了WorkerThread當(dāng)前狀態(tài)和對(duì)應(yīng)的FEvent對(duì)象,用于管理WorkerThread的Sleep、Running等狀態(tài)轉(zhuǎn)換,存儲(chǔ)在WorkerEvents中。

      • 然后創(chuàng)建一個(gè)Local任務(wù)隊(duì)列,用于存儲(chǔ)Task,存在WorkerLocalQueues數(shù)組中。

      • 最后通過CreateWorker創(chuàng)建一個(gè)線程,用FThread包裝,存儲(chǔ)在WorkerThreads數(shù)組。線程函數(shù)是FScheduler::WorkerMain,主要任務(wù)從Task隊(duì)列中取出Task并執(zhí)行。

      對(duì)于ForegroundWorker和BackgroundWorker,一些參數(shù)會(huì)有不同。

      除了專門的WorkerThread,GameThread也能作為WorkerThread使用,可以把一些Task指定到GameThread執(zhí)行,具體會(huì)在下面介紹。

      1. 添加任務(wù)

      觀察Async函數(shù),首先調(diào)用CreateTask,創(chuàng)建一個(gè)FConstructor對(duì)象,內(nèi)部包裝一個(gè)TGraphTask實(shí)例。TGraphTask創(chuàng)建時(shí)可以指定前置Task,但Async函數(shù)的任務(wù)是輕量的異步任務(wù),沒有前置,因此這里直接用NULL。TGraphTask接受模板參數(shù)TTask,這里為TAsyncGraphTask。



      TAsyncGraphTask

      TAsyncGraphTask是用戶自定義Task,可以把一個(gè)Lambda函數(shù)派發(fā)到WorkerThread或者GameThread上執(zhí)行。

      DoTask函數(shù)


      GetDesiredThread函數(shù),可以在構(gòu)造函數(shù)中傳入想執(zhí)行的線程。


      然后執(zhí)行ConstructAndDispatchWhenReady,先構(gòu)造一個(gè)TAsyncGraphTask實(shí)例,設(shè)置到TGraphTask.TaskStorage指針上。然后執(zhí)行Setup函數(shù),其中一些操作是GraphTask前置和后置相關(guān)的,先不管,最后會(huì)進(jìn)入QueueTask函數(shù),把任務(wù)添加到TaskGraph執(zhí)行。


      注意到這里用了FConstructor作為Helper類,把難寫的TaskStorage原地構(gòu)造包在里面,更易使用。

      FConstructor還有另一個(gè)函數(shù)ConstructAndHold,這可以先創(chuàng)建TGraphTask,但不執(zhí)行,后面通過手動(dòng)調(diào)用TGraphTask::Unlock執(zhí)行,但這種用法不多。


      GraphTask也有一個(gè)優(yōu)先級(jí)類型,為ETaskPriority,這里首先會(huì)根據(jù)GraphTask希望執(zhí)行的線程類型,得到對(duì)應(yīng)的TaskPriority,AnyThread對(duì)應(yīng)的就是Normal。

      Task->GetTaskHandle()獲取了GraphTask內(nèi)部的FTask對(duì)象,Init操作用于把Priority和封裝的Lambda函數(shù)參數(shù)賦值進(jìn)去,初始化FTask對(duì)象。

      最后TryLaunch會(huì)進(jìn)入FSchedule,把FTask加入到任務(wù)隊(duì)列中。



      任務(wù)隊(duì)列分為Thread Local和Global兩種,Async函數(shù)場(chǎng)景會(huì)加入Global,TaskGraph任務(wù)隊(duì)列特點(diǎn)是無鎖,即使多生產(chǎn)者,多消費(fèi)者,也不需要加CriticalSection級(jí)別的鎖,只使用原子操作。關(guān)于無鎖任務(wù)隊(duì)列,會(huì)在下面專門介紹。

      WakeUpWorker后面再看。

      至此,用戶提供的Task已經(jīng)被加入到任務(wù)隊(duì)列。

      2. 執(zhí)行任務(wù)

      首先看創(chuàng)建Worker Trhead的線程函數(shù)WorkerMain:


      參數(shù)含義:

      • WorkerEvent:線程對(duì)應(yīng)的SleepEvent,存在Scheduler數(shù)組中。

      • ExternalWorkerLocalQueue:存Task的LocalQueue,當(dāng)前WorkerThread獨(dú)占,存在Scheduler數(shù)組中。

      • WaitCycles:線程短等待的YieldCycles,不同WorkerThread會(huì)有些差異,避免大家一起執(zhí)行YieldCycles。

      • bPermitBackgroundWork:BackgroundWorker為true,F(xiàn)oregroundWorker為false。

      然后是一個(gè)大While循環(huán),不斷從Task隊(duì)列中取Task執(zhí)行,沒有Task則進(jìn)入Sleep。這里涉及到一些細(xì)節(jié),首先看到Worker隊(duì)列有很多種,然后線程也不是簡(jiǎn)單的沒Task就進(jìn)入Sleep,而是有更多狀態(tài)切換,以達(dá)到更好性能。


      先忽略Task隊(duì)列的細(xì)節(jié),因?yàn)檫@涉及到無鎖隊(duì)列的實(shí)現(xiàn),認(rèn)為從一個(gè)邏輯上的隊(duì)列里取Task,進(jìn)入TryExecuteTaskFrom函數(shù)。最終進(jìn)入ExecuteTask函數(shù),執(zhí)行用戶提供的Task,返回值A(chǔ)nyExecuted表示是否執(zhí)行了Task。


      Task處理完后不直接用WaitEvent進(jìn)入Wait,TaskGraph里增加了一個(gè)Drowsing(休眠)狀態(tài),總共有三個(gè)狀態(tài),狀態(tài)通過FSleepEvent結(jié)構(gòu)體維護(hù),轉(zhuǎn)換邏輯在TrySleeping函數(shù)。

      Running:正在執(zhí)行Task。

      Drowsing:隊(duì)列中Task剛執(zhí)行完不久,執(zhí)行WorkerSpinCycles次的主動(dòng)YieldCycles函數(shù),釋放一點(diǎn)CPU時(shí)間片,估計(jì)為了避免頻繁調(diào)用Wait和Trigger。進(jìn)入Drowsing會(huì)把FSleepEvent加入SleepEventStack容器,認(rèn)為已經(jīng)處于不活躍狀態(tài),需要通過WakeUpWorker調(diào)用從容器中移除,改回Running。

      Sleeping:一段時(shí)間的Drowsing狀態(tài)內(nèi)沒有執(zhí)行新的Task,調(diào)用FEvent.Wait,線程進(jìn)入阻塞狀態(tài)。只有通過WakeUpWorker函數(shù)執(zhí)行FEvent.Trigger后才能恢復(fù)執(zhí)行,同時(shí)會(huì)把FSleepEvent從SleepEventStack中彈出,把狀態(tài)改回Running。

      狀態(tài)轉(zhuǎn)換圖如下:


      3. Task優(yōu)先級(jí)

      游戲運(yùn)行過程中會(huì)產(chǎn)生大量Task,UE支持為Task指定多個(gè)優(yōu)先級(jí),提供更細(xì)粒度的控制,雖然在Async函數(shù)里只提供了一種優(yōu)先級(jí)。這里只討論Task在WorkerThread中執(zhí)行的情況,GameThread和RenderThread執(zhí)行Task另外再討論。

      Task優(yōu)先級(jí)定義如下:


      真正有意義的是High、Normal、BackgroundHigh、BackgroundNormal和BackgroundLow五種,運(yùn)行時(shí)會(huì)按照優(yōu)先級(jí)維護(hù)多個(gè)隊(duì)列,按照優(yōu)先級(jí)順序執(zhí)行這些Task。

      但用戶不能直接指定Task的優(yōu)先級(jí)。用戶自定義Task可以通過GetDesiredThread函數(shù)指定希望執(zhí)行的線程、線程優(yōu)先級(jí)、以及Background Task的優(yōu)先級(jí),最終會(huì)設(shè)置在TGraphTask的ThreadToExecuteOn屬性上。

      這個(gè)int32中嵌入了很多信息:


      ENamedThreads的組成如下,按比特位劃分了不同區(qū)域,具體也可看enum定義,這里過長(zhǎng)不貼了。


      • ThreadId部分8位

      標(biāo)識(shí)線程的ID,NamedThread下標(biāo)從0開始,StatsThread=0,RHIThread=1,AudioThread=2,GameThread=3,AnyThread=0xff。

      • QueueIndex部分1位

      MainQueue=1,LocalQueue=2。

      • ThreadPriority部分2位

      指定不同線程優(yōu)先級(jí),也可以認(rèn)為是Task的粗粒度優(yōu)先級(jí),NormalThreadPriority=0,HighThreadPriority=1,BackgroundThreadPriority=2。

      • TaskPriority部分1位

      用戶定義的Task細(xì)粒度優(yōu)先級(jí),僅對(duì)ThreadPriority=BackgroundThreadPriority時(shí)有效,把BackgroundThreadPriority再細(xì)分,NormalTaskPriority=0,HighTaskPriority=1。

      注意ThreadId的AnyThread選項(xiàng),表示在任意Worker線程執(zhí)行,但之前介紹過Worker線程分為ForgroundWorker和BackgroundWorker,它們線程優(yōu)先級(jí)不同,Task具體在哪類Worker中執(zhí)行,還是要看根據(jù)ENamedThreads得到的TaskPriority。

      多個(gè)枚舉可以組合,引擎提供了一些預(yù)置enum,目前并不是所有組合都支持,比如AnyHiPriThreadNormalTask和AnyHiPriThreadHiPriTask是等同的,只是先都定義了。

      以AnyBackgroundThreadNormalTask為例,該Task會(huì)在WorkerThread中執(zhí)行,線程TaskPriority是BackgroundNormal,用戶定義TaskPriority是NormalTaskPriority。


      UE也提供了一些Helper函數(shù),從中獲取信息:

      • GetThreadIndex

      • GetQueueIndex

      • GetTaskPriority

      • GetThreadPriorityIndex

      最終的TaskPriority和WorkerThread種類由ThreadPriority和用用戶定義TaskPriority共同決定,代碼在FTaskGraphCompatibilityImplementation::QueueTask中,整理的對(duì)應(yīng)關(guān)系如下:


      TaskQueue也按照TaskPriority數(shù)量進(jìn)行了劃分,各優(yōu)先級(jí)有自己的容器。TaskQueue分為Thread Local LocalQueue和全局的OverflowQueues,定義如下,是個(gè)ETaskPriority::Count的數(shù)組:


      以O(shè)verflowQueues為例,添加Task代碼如下:


      取Task代碼如下,優(yōu)先級(jí)從高到低遍歷:


      總結(jié)一下,TaskGraph提供線程池功能時(shí)執(zhí)行流程圖如下,這里TAsyncGraphTask也可以換成我們自己寫的用戶Task,同樣使用TGraphTask ::CreateTask().ConstructAndDispatchWhenReady接口即可。


      四、TaskGraph管理NamedThread

      TaskGraph不僅可以創(chuàng)建WorkerThread執(zhí)行任務(wù),還能把GameThread、RenderThread等專用線程也納入管理,分派任務(wù)給線程執(zhí)行。

      回顧FTaskGraphCompatibilityImplementation定義,其中包含了NameThreads容器,用一個(gè)FWorkerThread代表一個(gè)NamedThread。


      NamedThread線程ID定義如下,有RHIThread、AudioThrad、GameThread和RenderThread四個(gè)。


      1. FWorkerThread

      表示一個(gè)線程,包含相關(guān)信息,目前實(shí)現(xiàn)只用于NamedThread。

      成員

      • FTaskThreadBase*TaskGraphWorker:真正的TaskGraphWorker。

      • bool bAttached:NameThread是否被注冊(cè)到TaskGraph系統(tǒng)。

      2. FTaskThreadBase

      用于讓NamedThread有執(zhí)行GraphTask的能力。

      成員

      • ENamedThreads::Type ThreadId:線程ID。

      • Uint32 PerThreadIDTLSSlot:FWorkerThread對(duì)象指針會(huì)被存儲(chǔ)到這個(gè)Slot對(duì)應(yīng)的TLS中,這樣NamedThread就能取到它了。

      • TArray NewTasks:這個(gè)線程要執(zhí)行的Task。

      • FWorkerThread*OwnerWorker:所有者FWorkerThread的指針。

      函數(shù)

      ProcessTasksUntilQuit

      • ProcessTasksUntilIdle:兩個(gè)都用于讓NameThreads不斷執(zhí)行Task,直到線程Idle或者設(shè)置RequestQuit標(biāo)記。

      • EnqueueFromThisThread:向線程添加GraphTask任務(wù),當(dāng)前執(zhí)行的線程就是NamedThread。

      • EnqueueFromOtherThread:效果同上,當(dāng)前執(zhí)行線程不是NamedThread。

      • Run:內(nèi)部執(zhí)行ProcessTasksUntilQuit。

      3. FNamedTaskThread

      繼承自FTaskThreadBase,用于管理NamedTask。

      成員

      FThreadTaskQueue Queues[ENamedThreads::NumQueues]:存儲(chǔ)Task的隊(duì)列,分MainQueue和LocalQueue兩個(gè)。

      函數(shù)

      覆寫了ProcessTasksUntilQuit,ProcessTasksUntilIdle,EnqueueFromOtherThread。

      4. FThreadTaskQueue

      NamedTaskThread擁有的Task隊(duì)列。

      • FStallingTaskQueue StallQueue:包裝了兩個(gè)LockFreelist,對(duì)應(yīng)High和Normal兩個(gè)優(yōu)先級(jí),NamedThread的Task只有這兩個(gè)優(yōu)先級(jí)。

      • FEvent*StallRestartEvent:當(dāng)線程執(zhí)行完Task后,在該Event上等待

      五、創(chuàng)建FWorkerThread對(duì)象

      在TaskGraph Startup時(shí),會(huì)根據(jù)NameThreads數(shù)量,創(chuàng)建對(duì)應(yīng)的FWorkerThread對(duì)象,存儲(chǔ)在NamedThreads數(shù)組中。FWorkerThread初始化主要有兩個(gè)參數(shù):一個(gè)是分配的TLS Slot,用來存它,另一個(gè)是FNamedTaskThread對(duì)象。


      六、GameThread注冊(cè)到TaskGraph

      當(dāng)前線程調(diào)用AttachToThread函數(shù)可以把自己注冊(cè)到TaskGraph中,需要提供一個(gè)線程ID。

      這是GameThread的注冊(cè)方式,在Startup后就立即注冊(cè)了:


      接著執(zhí)行到這里,先根據(jù)CurrentThread ID獲取到對(duì)應(yīng)的TaskGraphWorker,然后調(diào)用InitializeForCurrentThread,該函數(shù)會(huì)把OwnerWorker存儲(chǔ)在PerThreadIDTLSSlot的TLS中。



      這樣就完成了注冊(cè)。

      其他幾個(gè)NamedThread也用同樣的方式注冊(cè)。

      七、向NameThread添加Task任務(wù)

      使用Async函數(shù)可以向GameThread添加Task,把參數(shù)設(shè)為EAsyncExecution::TaskGraphMainThread即可。往后的CreateTask等流程都相同,區(qū)別只在最后的QueueTask。


      這里傳入的InThreadToExecuteOn為GameThread,InCurrentThreadIfKnown沒有設(shè)置,默認(rèn)為AnyThread,也可以工作。

      QueueToExecuteOn表示希望加在MainQueue還是LocalQueue,在外部可以設(shè)置。

      比較值得注意的GetCurrentThread函數(shù),需要得到當(dāng)前線程ID,用ENamedThreads表示。


      如果是NamedThread,已經(jīng)設(shè)置了TLS,從中取出FWorkerThread指針,然后得到在NamedThreads中的偏移,就是ThreadId。

      如果是AnyThread,還會(huì)先嘗試獲取當(dāng)前線程上的ActiveTask,然后獲取ThreadPriority和TaskPriority,一并返回。

      最后根據(jù)ThreadToExecuteOn和CurrentThreadId,調(diào)用EnqueueFromThisThread或EnqueueFromOtherThread,這兩個(gè)接口區(qū)別為前者是當(dāng)前線程調(diào)用的,后者可以由其他線程調(diào)用,也可以由當(dāng)前線程調(diào)用,多了一步線程喚醒操作。

      EnqueueFromThisThread把Task加到Queues容器中,QueueIndex決定是MainQueue還是LocalQueue,默認(rèn)MainQueue,然后從之前的ThreadIdAndIndex里獲取到TaskPriority,決定加到內(nèi)部的HighPriority還是NormalPriority Task容器。


      EnqueueFromOtherThread也會(huì)先把Task加入StallQueue,然后看是否有ThreadToStart,有則調(diào)用Trigger,喚醒線程。


      八、NamedThread執(zhí)行Task

      以GameThread為例,看如何執(zhí)行TaskGraph中的Task。

      GameThread每幀都會(huì)通過World::Tick函數(shù),執(zhí)行各種Actor的Tick,驅(qū)動(dòng)游戲世界,而各種Tick函數(shù)又通過FTickTaskManager管理,背后再轉(zhuǎn)換成一個(gè)個(gè)TGraphTask,放到TaskGraph中執(zhí)行。

      直接進(jìn)入FTickTaskSequencer::ReleaseTickGroup函數(shù),這里會(huì)執(zhí)行一個(gè)TickGroup中全部的Tick,代碼如下:


      然后進(jìn)入WaitUntilTasksComplete函數(shù),執(zhí)行這些Task。WaitUntilTasksComplete含義是等待這些Task執(zhí)行完,方法為創(chuàng)建一個(gè)FReturnGraphTask,并把要等待的Task設(shè)為前置,F(xiàn)ReturnGraphTask作用是把FNamedTaskThread.Queue.QuitForReturn設(shè)為true,讓TaskGraph執(zhí)行完這些Task后就返回。

      WaitUntilTasksComplete


      之后執(zhí)行到ProcessTasksUntilQuit和ProcessTasksNamedThread,不斷從Queue中取GraphTask并執(zhí)行,直到執(zhí)行了FReturnGraphTask,然后返回。



      我們之前通過Async函數(shù)向GameThread添加的Task,也是在這里從Queue中取出,然后被執(zhí)行的。

      再借用一張圖,描述NamedThreads執(zhí)行Task的過程:


      九、GraphTask的依賴關(guān)系

      TaskGraph區(qū)別于普通線程池的一大特點(diǎn),就是GraphTask能存在前置依賴,這樣可以自定義Task的執(zhí)行順序,多線程動(dòng)畫、多線程GC等都是這樣實(shí)現(xiàn)的。

      GraphTask依賴關(guān)系需要解決兩個(gè)問題:

      • 如何組織Task,按照依賴順序執(zhí)行這些Task;

      • 等待依賴的Task執(zhí)行完成會(huì)可能造成線程休眠,如何喚醒線程。

      以多線程動(dòng)畫更新為示例,看如何建立Task間依賴。動(dòng)畫多線程更新可以把動(dòng)畫的Update、Evaluate開銷都放到WorkerThread中,減輕GameThread負(fù)擔(dān),當(dāng)SkeletalMeshComponent多時(shí)尤為明顯。


      首先創(chuàng)建一個(gè)FParallelAnimationEvaluationTask,用來做動(dòng)畫多線程Update和Evaluate,派發(fā)到WorkerThread上執(zhí)行。然后創(chuàng)建一個(gè)FParallelAnimationCompletionTask,用來做動(dòng)畫更新后的PostAnimEvaluation,在GameThread上執(zhí)行,前置為FParallelAnimationEvaluationTask,這一切都發(fā)生在PrePhysics tick階段。

      簡(jiǎn)單時(shí)序圖如下:


      1. GraphEvent

      這里Task依賴通過FGraphEventArray結(jié)構(gòu)實(shí)現(xiàn),而FGraphEventArray其實(shí)是一組FGraphEvent的引用,F(xiàn)GraphEvent是Task依賴的關(guān)鍵。


      GraphEvent可以理解為GraphTask相關(guān)的“事件”,GraphTask之間通過“事件”聯(lián)系。

      2. FGraphEvent

      包含了一系列后置Task,該GraphEvent是它們的觸發(fā)條件。

      成員

      • TClosableLockFreePointerListUnorderedSingleConsumer SubsequentList:后置Task,是無鎖鏈表。

      • FGraphEventArray EventsToWaitFor:該GraphEvent要等待的其他GraphEvent數(shù)組,其實(shí)只有一個(gè)元素,其他GraphEvent完成后,該EventGraph才會(huì)觸發(fā),在DontCompleteUntil里設(shè)置。

      方法

      • AddSubsequent:添加一個(gè)后置Task。

      • DontCompleteUntil:提供一個(gè)前置GraphEvent,前置完成后自己才觸發(fā)。

      回顧一下TGraphTask的成員:

      • Subsequents:該GraphTask對(duì)應(yīng)的FGraphEvent。

      • NumberOfPrerequistitesOutstanding:該GraphTask有多少個(gè)前置待執(zhí)行。

      • ConstructAndDispatchWhenReady函數(shù)會(huì)返回GraphTask對(duì)應(yīng)的GraphEvent,外部就能操作它了。

      3. CreateTask

      CreateTask方法可以接受Prerequistes參數(shù),得到該GraphTask的前置,接著進(jìn)入TGraphTask::SetupPrereqs函數(shù)。



      會(huì)通過AddSubsequent函數(shù)把自己添加到所有Prerequisties的后置里,然后會(huì)判斷Prerequisties是否都完成了,完成后才通過QueueTask把該GraphTask加到Task隊(duì)列里,等待執(zhí)行,大部分情況都不會(huì)進(jìn)入,需要等待前置。

      4. DispatchSubsequents

      在TGraphTask執(zhí)行完后,會(huì)通過Subsequents對(duì)象執(zhí)行DispatchSubsequents,讓其他依賴自己的Task執(zhí)行。這里要分有無EventsToWaitFor的情況。

      無EventsToWaitFor:

      TGraphTask執(zhí)行完后,就立即觸發(fā)完成事件,需要遍歷所有SubsequentList里的后置Task,調(diào)用ConditionalQueueTask,如果后置的所有前置都已被觸發(fā),就調(diào)用QueueTask,把自己加入Task隊(duì)列,等待執(zhí)行。



      有EventsToWaitFor:

      有時(shí)候TGraphTask自己完成了,但不想立即觸發(fā)事件,還想等待另一個(gè)GraphEvent完成后再觸發(fā),

      比如多線程動(dòng)畫更新里的TickFunction函數(shù),對(duì)應(yīng)的事件要等到TickCompletionEvent完成后再觸發(fā)。相當(dāng)于TickFunction Task已經(jīng)在執(zhí)行了,但還想給它添加前置一樣。



      這個(gè)操作通過增加一個(gè)NullGraphTask完成,這個(gè)Task繼承了自己的Subsequents,并且把EventsToWaitFor作為自己前置,本身的ExecuteTask并沒有任何邏輯,只是為了觸發(fā)原本的后置Task。


      回到動(dòng)畫多線程更新的例子,用圖表展示執(zhí)行流程和GraphTask、GraphEvent的工作過程:


      這只是簡(jiǎn)單的TaskGraph依賴關(guān)系,當(dāng)然可以自己組合出一些多前置,多后置的TaskGraph依賴,背后原理是一樣的。

      十、NamedThread Sleep/喚醒

      多線程動(dòng)畫例子中,如果FParallelAnimationEvaluationTask執(zhí)行時(shí)間過長(zhǎng),GameThread已經(jīng)把PrePhysics階段的所有Tick都執(zhí)行完了,就會(huì)進(jìn)入Sleep狀態(tài),等FParallelAnimationEvaluationTask執(zhí)行完后再喚醒GameThread繼續(xù)執(zhí)行。

      1. 進(jìn)入Sleep

      GameThread在Tick時(shí)會(huì)執(zhí)行ProcessTasksNamedThread,While循環(huán)從Queue中獲取下一個(gè)Task,執(zhí)行到ReturnTask之前都不會(huì)退出,如果取不到Task了,說明需要等其他線程執(zhí)行完前置Task,那么GameThrad自身會(huì)在這個(gè)Queue的StallRestartEvent上Wait,進(jìn)入Sleep狀態(tài)。


      StallQueue有設(shè)計(jì),可以用一個(gè)uint64記錄線程是否在StallRestartEvent上Wait,目前支持一個(gè)線程,因?yàn)镾tallQueue也是單個(gè)FNamedTaskThread對(duì)象獨(dú)有的,但看代碼是想設(shè)計(jì)成支持26個(gè)線程。

      看下StallQueue的Pop函數(shù):


      當(dāng)沒能獲取到新的Task時(shí),表示當(dāng)前Thread要進(jìn)入Wait了,會(huì)修改MasterState,記錄下這個(gè)線程。MasterState是一個(gè)巧妙的uint64位結(jié)構(gòu),可以同時(shí)記錄多線程訪問信息和等待的線程信息,結(jié)構(gòu)如下:


      Counter用于多線程保護(hù),每次進(jìn)Pop和Push都會(huì)加1,在修改Ptrs前都會(huì)比較一下Counter是否和進(jìn)函數(shù)時(shí)相同,防止Pop和Push在不同線程被執(zhí)行,導(dǎo)致判斷不正確。

      當(dāng)Counter判斷通過,就會(huì)把Ptrs的MyThread位設(shè)置為1,表示這個(gè)線程在StallRestartEvent上Wait了,目前MyThread固定為0。

      2. 喚醒

      當(dāng)調(diào)用EnqueueFromOtherThread添加Task后,會(huì)判斷線程是否在Sleep狀態(tài),然后執(zhí)行StallRestartEvent.Trigger()喚醒線程,繼續(xù)執(zhí)行。


      StallQueue的Push函數(shù)如下:


      會(huì)從MasterState中尋找Ptrs里被設(shè)置為1的位,表示哪些線程在上面Wait,得到ThreadToWake,外層函數(shù)再對(duì)其調(diào)用Trigger喚醒。

      十一、一些Task同步函數(shù)

      當(dāng)發(fā)出多個(gè)Task,分派到不同線程執(zhí)行后,邏輯上通常希望能對(duì)這些Task做些同步操作,比如在一個(gè)時(shí)間點(diǎn)等待這些Task都執(zhí)行完,或者像動(dòng)畫多線程例子那樣給TickFunction加WaitEvent,TaskGraph框架提供了多種這樣的函數(shù)。

      1. TaskGraph接口

      • WaitUntilTasksComplete(Tasks)

      等待多個(gè)GraphEvent執(zhí)行完,內(nèi)部做法是增加一個(gè)FReturnTask,把傳入的Tasks作為其前置,然后調(diào)用ProcessThreadUntilRequestReturn。

      比如如下代碼:



      • ProcessThreadUntilIdle

      在NamedThread上調(diào)用,阻塞執(zhí)行當(dāng)前Queue里的所有Task,直到完成。

      • ProcessThreadUntilRequestReturn

      與ProcessThreadUntilIdle類似,只是需要預(yù)先添加一個(gè)ReturnTask任務(wù)。

      ProcessThreadUntilIdle和ProcessThreadUntilRequestReturn兩個(gè)函數(shù)通常只有引擎會(huì)使用,項(xiàng)目代碼里感覺沒這個(gè)需求。

      2. GraphEvent接口

      • DontCompleteUntil

      GraphEvent的函數(shù),之前動(dòng)畫藍(lán)圖例子已介紹過,會(huì)給當(dāng)前GraphEvent設(shè)置另一個(gè)Event作為EventsToWaitFor,在EventsToWaitFor觸發(fā)后,才觸發(fā)當(dāng)前的GraphEvent。

      流程圖見上面。

      • Wait

      內(nèi)部調(diào)用了TaskGraph的WaitUntilTasksComplete接口,把自己作為參數(shù)傳入,效果與WaitUntilTasksComplete相同。

      文末,再次感謝南京周潤(rùn)發(fā) 的分享, 作者主頁:https://www.zhihu.com/people/xu-chen-71-65, 如果您有任何獨(dú)到的見解或者發(fā)現(xiàn)也歡迎聯(lián)系我們,一起探討。(QQ群: 793972859 )。


      近期精彩回顧




      特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺(tái)“網(wǎng)易號(hào)”用戶上傳并發(fā)布,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。

      Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

      相關(guān)推薦
      熱點(diǎn)推薦
      反轉(zhuǎn)了!臭豆腐賠償?shù)轿淮箢^被捐,女兒成眾矢之的,再找工作難了

      反轉(zhuǎn)了!臭豆腐賠償?shù)轿淮箢^被捐,女兒成眾矢之的,再找工作難了

      離離言幾許
      2026-03-01 12:08:32
      美以對(duì)伊朗動(dòng)手,俄羅斯、歐盟、巴基斯坦等多方發(fā)聲

      美以對(duì)伊朗動(dòng)手,俄羅斯、歐盟、巴基斯坦等多方發(fā)聲

      參考消息
      2026-02-28 22:08:44
      砍9分11籃板!中國(guó)男籃1米98奇兵正負(fù)值+21:郭士強(qiáng)用對(duì)他

      砍9分11籃板!中國(guó)男籃1米98奇兵正負(fù)值+21:郭士強(qiáng)用對(duì)他

      李喜林籃球絕殺
      2026-03-01 18:48:10
      狗咬人被摔死,狗主人帶9人復(fù)仇被60歲老人反殺!是正當(dāng)防衛(wèi)嗎?

      狗咬人被摔死,狗主人帶9人復(fù)仇被60歲老人反殺!是正當(dāng)防衛(wèi)嗎?

      何慕白
      2025-11-13 10:22:09
      椰樹太顛了!代言人婚紗照成網(wǎng)紅打卡地,“土味營(yíng)銷”天花板!

      椰樹太顛了!代言人婚紗照成網(wǎng)紅打卡地,“土味營(yíng)銷”天花板!

      LOGO研究所
      2026-03-01 11:19:49
      周末信息如何影響市場(chǎng)?明天是紅色星期一?還是黑色星期一?

      周末信息如何影響市場(chǎng)?明天是紅色星期一?還是黑色星期一?

      春江財(cái)富
      2026-03-01 08:39:41
      7億成本,《鏢人》虧損2億,吳京不服氣,一口氣立項(xiàng)了7部武俠片

      7億成本,《鏢人》虧損2億,吳京不服氣,一口氣立項(xiàng)了7部武俠片

      電影票房預(yù)告片
      2026-02-26 23:39:18
      你有知道哪些炸裂的秘密?網(wǎng)友:我有個(gè)秘密說出來肯定大家要笑死

      你有知道哪些炸裂的秘密?網(wǎng)友:我有個(gè)秘密說出來肯定大家要笑死

      帶你感受人間冷暖
      2026-01-29 00:10:05
      潛伏11年,那些被礦渣喂大的香蕉,終于開始向人類“復(fù)仇”了

      潛伏11年,那些被礦渣喂大的香蕉,終于開始向人類“復(fù)仇”了

      墨印齋
      2026-02-26 08:32:20
      金靖自曝過完年胖了6斤,怎么工作啊,網(wǎng)友調(diào)侃年味都長(zhǎng)身上了

      金靖自曝過完年胖了6斤,怎么工作啊,網(wǎng)友調(diào)侃年味都長(zhǎng)身上了

      韓小娛
      2026-03-01 09:57:40
      中東局勢(shì)升級(jí) 金價(jià)重回1600元 專家提醒:建議投資者逢高少量減持

      中東局勢(shì)升級(jí) 金價(jià)重回1600元 專家提醒:建議投資者逢高少量減持

      封面新聞
      2026-03-01 22:14:05
      抵債的方式能有多離譜?網(wǎng)友:賠了我八個(gè)車位

      抵債的方式能有多離譜?網(wǎng)友:賠了我八個(gè)車位

      另子維愛讀史
      2026-02-28 20:39:11
      網(wǎng)紅民宿降價(jià)也無人問津?這屆“摳門”的年輕人擠爆體制內(nèi)招待所

      網(wǎng)紅民宿降價(jià)也無人問津?這屆“摳門”的年輕人擠爆體制內(nèi)招待所

      藍(lán)鯨新聞
      2026-02-24 16:03:06
      中領(lǐng)館提醒: 18-65歲在俄長(zhǎng)期居留男性 須同意在俄軍事單位等至少服役1年

      中領(lǐng)館提醒: 18-65歲在俄長(zhǎng)期居留男性 須同意在俄軍事單位等至少服役1年

      閃電新聞
      2026-02-26 12:46:48
      男籃世預(yù)賽晉級(jí)形勢(shì)分析!中國(guó)隊(duì)1戰(zhàn)定生死:韓國(guó)或被聯(lián)手踢出局

      男籃世預(yù)賽晉級(jí)形勢(shì)分析!中國(guó)隊(duì)1戰(zhàn)定生死:韓國(guó)或被聯(lián)手踢出局

      籃球快餐車
      2026-03-01 00:42:22
      伊朗犯下10月7日的致命失誤,導(dǎo)致其失去一切

      伊朗犯下10月7日的致命失誤,導(dǎo)致其失去一切

      山河路口
      2026-03-01 20:25:18
      鄒市明一家國(guó)外度假,冉瑩穎挑染紅發(fā)似精神小妹,軒軒180cm超帥

      鄒市明一家國(guó)外度假,冉瑩穎挑染紅發(fā)似精神小妹,軒軒180cm超帥

      瘋說時(shí)尚
      2026-03-01 11:38:45
      美伊戰(zhàn)爭(zhēng)進(jìn)入第二天,美軍投入大量新式武器,伊朗連射20輪導(dǎo)彈

      美伊戰(zhàn)爭(zhēng)進(jìn)入第二天,美軍投入大量新式武器,伊朗連射20輪導(dǎo)彈

      鐵血戰(zhàn)史1927
      2026-03-01 18:37:15
      無錫二院!你要火了!

      無錫二院!你要火了!

      無錫eTV全媒體
      2026-03-01 11:46:51
      富士康創(chuàng)始人郭臺(tái)銘:“若兩岸爆發(fā)沖突,我會(huì)誓死守護(hù)臺(tái)灣”

      富士康創(chuàng)始人郭臺(tái)銘:“若兩岸爆發(fā)沖突,我會(huì)誓死守護(hù)臺(tái)灣”

      百態(tài)人間
      2026-02-12 15:21:00
      2026-03-01 23:32:49
      侑虎科技UWA incentive-icons
      侑虎科技UWA
      游戲/VR性能優(yōu)化平臺(tái)
      1552文章數(shù) 986關(guān)注度
      往期回顧 全部

      科技要聞

      榮耀發(fā)布機(jī)器人手機(jī)、折疊屏、人形機(jī)器人

      頭條要聞

      在以貼瓷磚的中國(guó)小伙:爆炸聲在頭頂響起 真的被嚇到

      頭條要聞

      在以貼瓷磚的中國(guó)小伙:爆炸聲在頭頂響起 真的被嚇到

      體育要聞

      火箭輸給熱火:烏度卡又輸斯波教練

      娛樂要聞

      黃景瑜 李雪健坐鎮(zhèn)!38集犯罪大劇來襲

      財(cái)經(jīng)要聞

      中東局勢(shì)升級(jí) 如何影響A股、黃金和原油

      汽車要聞

      理想汽車2月交付26421輛 歷史累計(jì)交付超159萬輛

      態(tài)度原創(chuàng)

      時(shí)尚
      家居
      本地
      游戲
      軍事航空

      今年春天最流行的4件衛(wèi)衣,照著穿就很好看

      家居要聞

      素色肌理 品意式格調(diào)

      本地新聞

      津南好·四時(shí)總相宜

      LPL季后賽:IG復(fù)仇NIP,成功挺進(jìn)下輪

      軍事要聞

      伊朗前總統(tǒng)內(nèi)賈德遇襲身亡

      無障礙瀏覽 進(jìn)入關(guān)懷版