首先查阅了WIKI中能找到Rama大神的两篇文章,讲了两个开线程的方式:
https://wiki.unrealengine.com/Multi-Threading:_Task_Graph_System
https://wiki.unrealengine.com/Multi-Threading:_How_to_Create_Threads_in_UE4
一篇是用任务系统实现的多线程,一篇是用的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()
{
}
};
根据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);
}
};
FAutoDeleteAsyncTask模板类的定义可以在AsyncWork.h中查看,这里就不贴了,使用多线程时,new出来即可:
void Example()
{
// start an example job
(new FAutoDeleteAsyncTask(5)->StartBackgroundTask();
// do an example job now, on this thread
(new FAutoDeleteAsyncTask(5)->StartSynchronousTask();
}
任务系统的多线程执行流程在http://blog.csdn.net/tuanxuan123/article/details/52780629里有具体分析,我们可以看到,其实任务系统最后创建的也是FRunnableThread这个线程类:
FQueuedThread::Create()
{
DoWorkEvent = FPlatformProcess::GetSynchEventFromPool();
Thread = FRunnableThread::Create(this, *PoolThreadName, InStackSize, ThreadPriority, FPlatformAffinity::GetPoolThreadMask());
}
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 object
NewThread = FPlatformProcess::CreateRunnableThread();
if (NewThread)
{
// Call the thread's create method
if (NewThread->CreateInternal(InRunnable,ThreadName,InStackSize,InThreadPri,InThreadAffinityMask) == false)
{
// We failed to start the thread correctly so clean up
delete 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 up
delete NewThread;
NewThread = nullptr;
}
}
#if STATS
if( NewThread )
{
FStartupMessages::Get().AddThreadMetadata( FName( *NewThread->GetThreadName() ), NewThread->GetThreadID() );
}
#endif // STATS
return NewThread;
}
if (FPlatformProcess::SupportsMultithreading())先判断平台是否支持多线程:
根据当前的平台会执行对应系统的CreateRunnableThread函数,我们是Window系统下:
FRunnableThread* FWindowsPlatformProcess::CreateRunnableThread()
{
return new FRunnableThreadWin();
}
创建了一个继承自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 first
ThreadInitSyncEvent = FPlatformProcess::GetSynchEventFromPool(true);
// Create the new thread
Thread = CreateThread(NULL,InStackSize,_ThreadProc,this,STACK_SIZE_PARAM_IS_A_RESERVATION,(::DWORD *)&ThreadID);
// If it fails, clear all the vars
if (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 event
FPlatformProcess::ReturnSynchEventToPool(ThreadInitSyncEvent);
ThreadInitSyncEvent = nullptr;
return Thread != NULL;
}
函数执行了具体的线程创建,并将新创建的线程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);
}
之后执行期间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