UE4--多线程的实现方式
首先查閱了WIKI中能找到Rama大神的兩篇文章,講了兩個開線程的方式:
https://wiki.unrealengine.com/Multi-Threading:_Task_Graph_System
https://wiki.unrealengine.com/Multi-Threading:_How_to_Create_Threads_in_UE4
TaskGraph與FRunnable的比較
一篇是用任務系統實現的多線程,一篇是用的FRunnable和FRunnableThread線程類。前者適用于相對較小的功能實現,或者是需要開多個線程,各自實現一塊較小的功能或者計算的情況;而后者才是適用于復雜運算或是較大的功能模塊的。
Rama用這兩種方法分別實現了一個計算前50000個質數的例子。任務系統中,每個線程只執行一個質數的計算,系統開了多個線程,其實效率是很低的;而采用了FRunnable則只開一個線程來執行整個質數計算過程,效率相對較高。Rama的例子中,前者運行時主游戲畫面只有45幀左右,而后者運行時能保持在90幀左右。
而且任務系統有時候會使用我們的游戲線程,因此對于大型的任務應該使用FRunnable
使用多線程的注意事項
當需要進行復雜繁多的計算時,我們需要開線程來計算以防止免游戲主線程卡死。但須注意,在GameThread線程之外的其他線程中,不允許做以下事情:
不要 spawn / modify / destroy UObjects / AActors
不要使用定時器 TimerManager(TimerManager只能在主線程中)
不要使用任何繪制接口,例如 DrawDebugLine/Point,不然有可能崩潰
在本科操作系統的課程中我們就學過,多進程或線程之間的數據交互可能會造成死鎖,為此才有了后面的銀行家算法等加鎖的算法…因此我們新開的線程中不允許做上述事情,一般只是用來實現復雜計算,網絡連接等功能…
還有一點要注意,當創建太多線程時,有可能達到CPU的并發上限,這時這些并發線程會為了爭奪CPU的執行時間而彼此阻礙,我們可以在FQueuedThreadPool中限制線程數量。
任務系統
從Engine\Source\Runtime\Core\Public\Async\AsyncWork.h開始分析。
AsyncWork.h中提供了兩個任務模板類:FAutoDeleteAsyncTask和FAsyncTask,區別是執行結束后是否自動銷毀。還為我們寫了對應的example,稍后我將講述FAutoDeleteAsyncTask的example,但是Rama大神的任務系統例子中并沒有使用這兩個模板類,看的有點懵逼=。=
其中的DoWork函數是任務執行具體內容的接口。引擎注釋:
Tells the user job to do the work, sometimes called synchronously, sometimes from the thread pool. Calls the event tracker.
通知用戶進程來work,有時是同步的,有時是從線程池中進行。會調用事件追蹤器。
AsyncWork.h中有一個不能被abandon的基類,我們自定義的任務類就是繼承自她的:
/*** Stub class to use a base class for tasks that cannot be abandoned*/ class FNonAbandonableTask { public:bool CanAbandon(){return false;}void Abandon(){} };- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
根據AsyncWork.h中提供的模板example,使用任務系統的流程:
先自定義一個任務類,用于執行我們的任務:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
FAutoDeleteAsyncTask模板類的定義可以在AsyncWork.h中查看,這里就不貼了,使用多線程時,new出來即可:
void Example() {// start an example job(new FAutoDeleteAsyncTask<ExampleAutoDeleteAsyncTask>(5)->StartBackgroundTask();// do an example job now, on this thread(new FAutoDeleteAsyncTask<ExampleAutoDeleteAsyncTask>(5)->StartSynchronousTask(); }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
任務系統的多線程執行流程在http://blog.csdn.net/tuanxuan123/article/details/52780629里有具體分析,我們可以看到,其實任務系統最后創建的也是FRunnableThread這個線程類:
FQueuedThread::Create() {DoWorkEvent = FPlatformProcess::GetSynchEventFromPool();Thread = FRunnableThread::Create(this, *PoolThreadName, InStackSize, ThreadPriority, FPlatformAffinity::GetPoolThreadMask()); }- 1
- 2
- 3
- 4
- 5
FRunnable
FRunnable:是線程使用類
FRunnableThread:是具體的線程類
實際創建新線程時,自定義一個類繼承自FRunnable,FRunnable中的Init(),Run(),Stop(),Exit()都是可供我們重寫的虛函數。
我們可以在構造函數中使用FRunnableThread::Create函數來創建一個新的線程。我們來看FRunnableThread::Create函數:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
if (FPlatformProcess::SupportsMultithreading())先判斷平臺是否支持多線程:
平臺支持多線程:
根據當前的平臺會執行對應系統的CreateRunnableThread函數,我們是Window系統下:
FRunnableThread* FWindowsPlatformProcess::CreateRunnableThread() {return new FRunnableThreadWin(); }- 1
- 2
- 3
- 4
創建了一個繼承自FRunnableThread的FRunnableThreadWin類,之后在CreateInternal函數中:
virtual bool CreateInternal( FRunnable* InRunnable, const TCHAR* InThreadName,uint32 InStackSize = 0,EThreadPriority InThreadPri = TPri_Normal, uint64 InThreadAffinityMask = 0 ) override{check(InRunnable);Runnable = InRunnable;ThreadAffinityMask = InThreadAffinityMask;// Create a sync event to guarantee the Init() function is called firstThreadInitSyncEvent = FPlatformProcess::GetSynchEventFromPool(true);// Create the new threadThread = CreateThread(NULL,InStackSize,_ThreadProc,this,STACK_SIZE_PARAM_IS_A_RESERVATION,(::DWORD *)&ThreadID);// If it fails, clear all the varsif (Thread == NULL){Runnable = nullptr;}else{FThreadManager::Get().AddThread(ThreadID, this);// Let the thread start up, then set the name for debug purposes.ThreadInitSyncEvent->Wait(INFINITE);ThreadName = InThreadName ? InThreadName : TEXT("Unnamed UE4");SetThreadName( ThreadID, TCHAR_TO_ANSI( *ThreadName ) );SetThreadPriority(InThreadPri);}// Cleanup the sync eventFPlatformProcess::ReturnSynchEventToPool(ThreadInitSyncEvent);ThreadInitSyncEvent = nullptr;return Thread != NULL;}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
函數執行了具體的線程創建,并將新創建的線程add到線程管理器FThreadManager中,并設置了相關的一些屬性。而我們的新線程在_ThreadProc回調函數已經開始執行。
平臺不支持多線程:
UE4會幫我們new一個假線程 NewThread = new FFakeThread();
FFakeThread也是繼承自FRunnableThread的線程類,FFakeThread的構造函數就會執行添加到FThreadManager的函數:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
之后執行期間FThreadManager會判斷系統是否支持多線程并執行相應流程。
擼主能力不夠,看了幾位大神的博客不太明白,好在老大幫我拆了FRunnable流程的代碼,因此記錄一下。如有錯誤或不當之處,還望各位大神不吝賜教!
參考:
http://blog.csdn.net/noahzuo/article/details/51372972
http://blog.csdn.net/tuanxuan123/article/details/52780629
https://wiki.unrealengine.com/Multi-Threading:_Task_Graph_System
https://wiki.unrealengine.com/Multi-Threading:_How_to_Create_Threads_in_UE4
總結
以上是生活随笔為你收集整理的UE4--多线程的实现方式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 首款骁龙8+竖折旗舰!三星Galaxy
- 下一篇: 运用计算机计算包含排斥原理,离散数学包含