首先查阅了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,使用任务系统的流程:
先自定义一个任务类,用于执行我们的任务:
class ExampleAutoDeleteAsyncTask : public FNonAbandonableTask
{friend class FAutoDeleteAsyncTask<ExampleAutoDeleteAsyncTask>;int32 ExampleData;//构造ExampleAutoDeleteAsyncTask(int32 InExampleData): ExampleData(InExampleData){}//自己定义任务具体执行内容void DoWork(){//... do the work here}FORCEINLINE TStatId GetStatId() const{RETURN_QUICK_DECLARE_CYCLE_STAT(ExampleAutoDeleteAsyncTask, STATGROUP_ThreadPoolAsyncTasks);}
};
- 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函数:
FRunnableThread* FRunnableThread::Create(
class FRunnable* InRunnable, const TCHAR* ThreadName,uint32 InStackSize,EThreadPriority InThreadPri, uint64 InThreadAffinityMask)
{FRunnableThread* NewThread = nullptr;if (FPlatformProcess::SupportsMultithreading()){check(InRunnable);// Create a new thread objectNewThread = FPlatformProcess::CreateRunnableThread();if (NewThread){// Call the thread's create methodif (NewThread->CreateInternal(InRunnable,ThreadName,InStackSize,InThreadPri,InThreadAffinityMask) == false){// We failed to start the thread correctly so clean updelete NewThread;NewThread = nullptr;}}}else if (InRunnable->GetSingleThreadInterface()){// Create a fake thread when multithreading is disabled.NewThread = new FFakeThread();if (NewThread->CreateInternal(InRunnable,ThreadName,InStackSize,InThreadPri) == false){// We failed to start the thread correctly so clean updelete NewThread;NewThread = nullptr;}}#if STATSif( NewThread ){FStartupMessages::Get().AddThreadMetadata( FName( *NewThread->GetThreadName() ), NewThread->GetThreadID() );}
#endif // STATSreturn NewThread;
}
- 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的函数:
FFakeThread(): bIsSuspended(false), Runnable(nullptr){ThreadID = ThreadIdCounter++;// Auto register with single thread manager.FThreadManager::Get().AddThread(ThreadID, this);}
- 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