UE4多线程任务系统详解

首先,了解一下该系统重要的数据类型.


1. FQueuedThreadPool:虚基类,队列线程池,  FQueuedThreadPoolBase继承自FQueuedThreadPool,
    FQueuedThreadPoolBase维护了一个TArray<IQueuedWork*> QueuedWork(需要被执行的工作),  TArray<FQueuedThread*> AllThreads(所       有的线程)
    TArray<FQueuedThread*> QueuedThreads ( 空闲的线程)

2. FQueuedThread继承自FRunnable,  代表了线程的执行体,但并非线程本身。一个线程创建后将会执行FRunnable的Run()函数.
3. FRunnableThread代表了线程本身,该类会派生出平台相关的子类,win32下对应的是FRunnableThreadWin,建议读者看看某个平台下的具体实     现。在创建时需要指定一个FRunnable,用于线程执行。
4. FEvent: 虚基类,提供了事件操作的接口,用于线程的激活挂起, 该类会派生出平台相关的子类,win32下对应的是FEventWin。
    两个重要接口: Wait()将会使线程挂起,Trigger()将会激活线程,配对使用。

了解了相关的数据类型后,可以开始剖析多线程任务系统的流程了,先从AsyncWork.h开始。
AsyncWork.h中有几个使用多线程任务系统的例子,先来了解我们如何使用多线程来执行任务。

大体流程如下:    
   先自定义一个任务类,用于执行我们的任务
   class ExampleAutoDeleteAsyncTask : public FNonAbandonableTask
    {
        friend class FAutoDeleteAsyncTask<ExampleAutoDeleteAsyncTask>;

        //核心接口,  这里便是任务的具体执行内容.
        void DoWork()
        {            
        }

    };

//任务模板类,其作用是托管我们自定义的任务类,如执行完任务后自动销毁.
template<typename TTask>
class FAutoDeleteAsyncTask : private IQueuedWork
{

    TTask Task;
    
    //核心接口,任务的执行从这里开始, 参数bForceSynchronous = false表示异步执行
    void Start(bool bForceSynchronous)
    {
        FPlatformMisc::MemoryBarrier();
        FQueuedThreadPool* QueuedPool = GThreadPool;
        if (bForceSynchronous)
        {
            QueuedPool = 0;
        }
        
        if (QueuedPool)
        {     //异步执行,把自身(IQueuedWork)添加到队列线程池中去执行
            QueuedPool->AddQueuedWork(this);
        }
        else
        {
            // 同步执行,直接调用Task.DoWork();
            DoWork();
        }
    }

    void DoWork()
    {
        Task.DoWork();
        delete this;
    }

    virtual void DoThreadedWork()
    {
        DoWork();
    }


    void StartSynchronousTask()
    {
        Start(true);
    }


    void StartBackgroundTask()
    {
        Start(false);
    }

};

    //使用多线程任务的例子
    void Example()
    {
        //异步执行Task
        (new FAutoDeleteAsyncTask<ExampleAutoDeleteAsyncTask>(5)->StartBackgroundTask();
        //同步执行Task
        (new FAutoDeleteAsyncTask<ExampleAutoDeleteAsyncTask>(5)->StartSynchronousTask();
    }

如何使用多线程来执行任务已经展示完了,就是这么easy!
接下来分析多线程来执行流程,重点是QueuedPool实例化时做了什么 以及 QueuedPool->AddQueuedWork(this);
我们先看一下QueuedPool实例是如何得到的:QueuedPool = GThreadPool;  而GThreadPool是在引擎初始化的时候创建的,代码在launchEngineLoop文件里,如下:
if (FPlatformProcess::SupportsMultithreading())
{         
        //可以看出,只有在系统支持多线程的情况下才会创建GThreadPool    
        GThreadPool    = FQueuedThreadPool::Allocate();
        int32 NumThreadsInThreadPool = FPlatformMisc::NumberOfWorkerThreadsToSpawn();
        verify(GThreadPool->Create(NumThreadsInThreadPool));
        。。。。。。
}

GThreadPool  创建之后便调用了它的Create
GThreadPool->Create(InNumQueuedThreads)
{
        // 主要是创建N个线程
        for (uint32 Count = 0; Count < InNumQueuedThreads && bWasSuccessful == true; Count++)
        {
            FQueuedThread* pThread = new FQueuedThread();           
            if (pThread->Create(this,StackSize,ThreadPriority) == true)
            {
                QueuedThreads.Add(pThread);
                AllThreads.Add(pThread);
            }
        }
    。。。。。。。    
}

在FQueuedThread的Create接口里,创建了平台相关的线程和系统事件,该线程的执行体是FQueuedThread的Run函数.
FQueuedThread::Create()
{
        DoWorkEvent = FPlatformProcess::GetSynchEventFromPool();
        Thread = FRunnableThread::Create(this, *PoolThreadName, InStackSize, ThreadPriority, FPlatformAffinity::GetPoolThreadMask());
        。。。。。。。
}

Create()之后,线程执行了Run函数。
然而,在线程初始化之后,Run会一直处在while循环中,不断等待DoWorkEvent->Trigger()
FQueuedThread::Run()
{
            bool bContinueWaiting = true;
            //在这个循环里,Wait()使线程不断挂起.
            while( bContinueWaiting )
            {                
                bContinueWaiting = !DoWorkEvent->Wait( 10 );
            }
            。。。。。。。
}

讲到这里,先小结一下,实例化GThreadPool实际上也就是创建了N个线程,每个线程都处于一个不断挂起,醒来,挂起的循环中。
这N个线程用于执行异步任务,用户需要执行自定义的任务可以参考AsyncWork.h的例子。

那么,一定在某个地方会调用DoWorkEvent->Trigger()使Run能够跳出死循环,执行后面的代码。
聪明的读者一定想到了在前面提到的 (GThreadPool等于QueuedPool)QueuedPool->AddQueuedWork(this), 没错,就是它,在AddQueuedWork里面便间接调用了DoWorkEvent->Trigger()

QueuedPool->AddQueuedWork(InQueuedWork)
{
        //取出一个空闲的线程执行InQueuedWork,没有则添加到QueuedWork中
        if (QueuedThreads.Num() > 0)
        {
            int32 Index = 0;
            Thread = QueuedThreads[Index];
            QueuedThreads.RemoveAt(Index);
        }
        if (Thread != nullptr)
        {
            //DoWork实际上只是触发了event,从而激活等待的线程
            Thread->DoWork(InQueuedWork);
        }
        else
            QueuedWork.Add(InQueuedWork);

}

FQueuedThread::DoWork(IQueuedWork* InQueuedWork)
{
        DoWorkEvent->Trigger();
}

调用DoWorkEvent->Trigger()之后,线程的Run()得以继续执行,接下来的工作便是不断取出Task来执行.
FQueuedThread::Run()
{
        。。。。。。。。
            IQueuedWork* LocalQueuedWork = QueuedWork         
            while (LocalQueuedWork)
            {
                //最终会执行Task.DoWork();
                LocalQueuedWork->DoThreadedWork();
                //取出下一个待执行的task或者把该FQueuedThread添加回空闲线程池中
                LocalQueuedWork = OwningThreadPool->ReturnToPoolOrGetNextJob(this);
            }
        
}

至此,整个多线程任务的执行流程便分析完了!
---------------------
作者:子轩Q
来源:CSDN
原文:https://blog.csdn.net/tuanxuan123/article/details/52780629
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值