[学习笔记]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()
    {
    }
};

根据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

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

你可能感兴趣的:(UE4学习,UE4,多线程,任务系统,FRunnable,AsyncWork)