在游戏客户端开发过程中难免有些任务是需要另外开一个线程来执行的,这些任务的特点是会有一些阻塞操作,它的执行不能影响到游戏线程的正常执行。比如一款网络游戏的客户端,那么网络的收发最好是使用单独的线程来处理,又比如在线更新数据的操作,也最好是使用单独的线程来操作才比较合理。
我在封装线程池之前想了解下UE4的多线程是怎么实现的,所以最近我看了它的源码,刚开始没有找到一个很好的入口,所以看起来云里雾里,还好通过慢慢的啃,总算是看出点门路了,在此分享一下自己的经验,避免其他同行陷入我之前的困境。
UE4采用的是半同步半异步线程池模型,在本文中我使用源码+应用的方式来分析下UE4的线程池,由于UE4是个较复杂的引擎,所以文中如果有出现错误的地方欢迎指正。
我们首先来查看引擎里给的一个线程池的使用例子,代码在Engine\Source\Runtime\Core\Public\Async\AsyncWork.h
class ExampleAutoDeleteAsyncTask : public FNonAbandonableTask
{
// 最终会调用此友元类的DoThreadedWork()来执行任务
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);
}
};
void Example()
{
// start an example job
(new FAutoDeleteAsyncTask<ExampleAutoDeleteAsyncTask>(5)->StartBackgroundTask();
// do an example job now, on this thread
(new FAutoDeleteAsyncTask<ExampleAutoDeleteAsyncTask>(5)->StartSynchronousTask();
}
FAutoDeleteAsyncTask类的声明,我们需要关注的是Start()函数,它是在Example中的两个Start***中调用的
void Start(bool bForceSynchronous)
{
FPlatformMisc::MemoryBarrier();
FQueuedThreadPool* QueuedPool = GThreadPool;
if (bForceSynchronous)
{
QueuedPool = 0;
}
if (QueuedPool)
{
QueuedPool->AddQueuedWork(this);
}
else
{
// we aren't doing async stuff
DoWork();
}
}
我们可以看到如果选择的是异步执行的任务,会把自己塞入到GThreadPool的QueuedPool中,其中GThreadPool就是UE4在启动的时候创建的,继续进入到AddQueuedWork()中