医院导航网站怎么做,企业展示网站建设需要做什么,ps软件免费版在哪下载,黑色企业网站详细参考#xff1a;《Exploring in UE4》多线程机制详解[原理分析] - 知乎 (zhihu.com)
UE4 C基础 - 多线程 - 知乎 (zhihu.com)
多线程的好处
通过为每种事件类型的处理分配单独的线程#xff0c;能够简化处理异步事件的代码。每个线程在进行事件处理时可以采用同步编程… 详细参考《Exploring in UE4》多线程机制详解[原理分析] - 知乎 (zhihu.com)
UE4 C基础 - 多线程 - 知乎 (zhihu.com)
多线程的好处
通过为每种事件类型的处理分配单独的线程能够简化处理异步事件的代码。每个线程在进行事件处理时可以采用同步编程模式同步编程模式要比异步编程模式简单得多。多个进程必须使用操作系统提供的复杂机制才能实现内存和文件描述符的共享。而多个线程自动地可以访问相同的储存地址空间和文件描述符。有些问题可以通过将其分解从而改善整个程序的吞吐量。在只有一个控制线程的情况下单个进程需要完成多个任务时实际上需要把这些任务串行化有了多个控制线程相互独立的任务的处理就可以交叉进行只需要为每个任务分配一个单独的线程当然只有在处理过程互不依赖的情况下两个任务的执行才可以穿插进行。交互的程序同样可以通过使用多线程实现响应时间的改善多线程可以把程序中处理用户输入输出的部分与其他部分分开。 UE4中的多线程
FRunnable
标准模板 //Runnable.h
class CORE_API FRunnable
{
public:virtual bool Init(){return true;}virtual uint32 Run() 0;virtual void Stop() { }virtual void Exit() { }virtual class FSingleThreadRunnable* GetSingleThreadInterface( ){return nullptr;}virtual ~FRunnable() { }
}; 实际上在实现多线程的时候我们需要将FRunnable作为参数传递到真正的线程里面然后才能通过线程去调用FRunnable的Run也就是我们具体实现的类的Run方法通过虚函数覆盖父类的Run。所谓真正的线程其实就是FRunnableThread不同平台的线程都继承自他 或者 UE4是跨平台的引擎对各个平台线程实现进行了封装抽象出了 FRunnable 。引擎中大部分的需要多线程执行逻辑都是继承这个类实现的多线程 #include HAL/Runnable.hclass MyRunnable : public FRunnable {
public:virtual bool Init() override; // 初始化 runnable 对象virtual uint32 Run() override; // 运行 runnable 对象virtual void Stop() override; // 停止 runnable 对象,线程提前终止时被调用virtual void Exit() override; // 退出 runnable 对象
};bool MyRunnable::Init() { return true; }
uint32 MyRunnable::Run() { return 0; }
void MyRunnable::Stop() {}
void MyRunnable::Exit() {} 调用顺序是 Init(), Run(), Exit()。Runnable对象初始化操作在 Init() 函数中完成并通过返回值确定是否成功。初始化失败则该线程停止执行并返回一个错误码成功则会执行 Run() 执行完毕后则会调用 Exit() 执行清理操作。 FRunnableThread Runnable负责具体业务逻辑的执行UE4中使用 FRunnableThread 表示一个可执行的线程。 可以通过调用 FRunnableThread::Create 完成线程的创建 #include HAL/RunnableThread.hstatic FRunnableThread * Create
(class FRunnable * InRunnable, // Runnable 对象const TCHAR * ThreadName, // 线程名称uint32 InStackSize, // 线程栈大小,0表示使用当前线程的栈大小EThreadPriority InThreadPri, // 线程优先级uint64 InThreadAffinityMask
);// 返回值若成功则返回创建的线程否则返回 nullptr样例代码如下 #include HAL/RunnableThread.hFRunnable * Runnable new MyRunnable();
FRunnableThread* RunnableThread FRunnableThread::Create(Runnable, TEXT(LaLaLaDeMaXiYa!));线程对象创建成功后即开始执行Runnable对象的 Init () 函数如果成功则分别执行Run() 和 Exit() 函数。
线程标识 每个线程都有一个线程ID线程ID在它所属的进程环境中有效。为增加标识性UE4还增加了线程名称。线程ID是唯一的线程名称可以重复。可通过GetThreadID 和 GetThreadName 获取线程ID和名称。
const uint32 GetThreadID() const;
const FString GetThreadName() const;
线程终止
单个线程可以通过如下三种方式退出。 线程执行完 runnable 对象的 Run() 和 Exit() 函数后正常退出调用 WaitForCompletion() 函数阻塞调用例程直到线程执行完毕调用 Kill(bool bShouldWaitfalse) 函数会先执行 runnable 对象的 stop 函数,然后根据 bShouldWait 参数决定是否等待线程执行完毕。如果不等待则强制杀死线程可能会造成内存泄漏。 void WaitForCompletion(); // 阻塞调用例程直到线程执行完毕
bool Kill(bool bShouldWait); // 强制杀掉线程
FThreadManager 通过FRunnableThread 创建的线程是通过 FThreadManager 进行统一管理。
// ThreadingBase.cpp FRunnableThread::Create 函数
// Call the threads create method
if (NewThread-CreateInternal(InRunnable,ThreadName,InStackSize,InThreadPri,InThreadAffinityMask) false)CreateInternal根据平台的不同实现不同常用平台中Android和iOS都是采用的 pthread标准线程库Windows平台是单独实现的。线程创建完毕后会统一调用
FThreadManager::Get().AddThread(ThreadID, this);将线程本身添加至管理器。如 WindowsRunnableThread.h FRunnableThreadWin::CreateInternal 函数。标准线程对象 FRunnableThreadPThread 则是在入口点:
virtual PthreadEntryPoint GetThreadEntryPoint() {return _ThreadProc;
}static void *STDCALL _ThreadProc(void *pThis) {check(pThis);FRunnableThreadPThread* ThisThread (FRunnableThreadPThread*)pThis;// cache the thread ID for this thread (defined by the platform)ThisThread-ThreadID FPlatformTLS::GetCurrentThreadId();// 这里将线程本身加入管理器 FThreadManager::Get().AddThread(ThisThread-ThreadID, ThisThread);// set the affinity. This function sets affinity on the current thread, so dont call in the Create function which will trash the main thread affinity.FPlatformProcess::SetThreadAffinityMask(ThisThread-ThreadAffinityMask); // run the thread!ThisThread-PreRun();ThisThread-Run();ThisThread-PostRun();pthread_exit(NULL);return NULL;
}
线程池 线程过多会带来调度开销进而影响缓存局部性和整体性能。频繁创建和销毁线程也会带来极大的开销。通常我们更加关心的是任务可以并发执行并不想管理线程的创建销毁和调度。通过将任务处理成队列交由线程池统一执行可以提升任务的执行效率。UE4提供了对应的线程池来满足我们的需求。异步任务统一都继承至 IQueuedWork属于抽象接口类可供我们直接使用的是
FAsyncTask 异步任务自动加入线程池FAutoDeleteAsyncTask 异步任务任务完成后会自动销毁 异步任务通常继承 FNonAbandonableTask表明该任务不可被抛弃必须被执行完毕。样例代码如下
idi#include Async/AsyncWork.hclass ExampleAsyncTask : public FNonAbandonableTask
{friend class FAsyncTaskExampleAsyncTask;friend class FAutoDeleteAsyncTaskExampleAsyncTask;int32 ExampleData;ExampleAsyncTask(int32 InExampleData): ExampleData(InExampleData){}void DoWork() {UE_LOG(LogBlankProgram, Display, TEXT(ExampleAsyncTask %d Work.), ExampleData);}FORCEINLINE TStatId GetStatId() const {RETURN_QUICK_DECLARE_CYCLE_STAT(ExampleAsyncTask, STATGROUP_ThreadPoolAsyncTasks);}
};void Example {// 2.1 线程池异步队列FAsyncTaskExampleAsyncTask* MyTask new FAsyncTaskExampleAsyncTask(1);// 交由后台控制任务开始执行时机MyTask-StartBackgroundTask();// 确保线程被执行完成MyTask-EnsureCompletion();delete MyTask;
} AsyncTask系统 AsyncTask系统是一套基于线程池的异步任务处理系统,样例如下 //AsyncWork.hclass ExampleAsyncTask : public FNonAbandonableTask{friend class FAsyncTaskExampleAsyncTask;int32 ExampleData;ExampleAsyncTask(int32 InExampleData): ExampleData(InExampleData){}void DoWork(){... do the work here}FORCEINLINE TStatId GetStatId() const{RETURN_QUICK_DECLARE_CYCLE_STAT(ExampleAsyncTask, STATGROUP_ThreadPoolAsyncTasks);}};void Example(){//start an example jobFAsyncTaskExampleAsyncTask* MyTask new FAsyncTaskExampleAsyncTask( 5 );MyTask-StartBackgroundTask();//--or --MyTask-StartSynchronousTask();//to just do it now on this thread//Check if the task is done :if (MyTask-IsDone()){}//Spinning on IsDone is not acceptable( see EnsureCompletion ), but it is ok to check once a frame.//Ensure the task is done, doing the task on the current thread if it has not been started, waiting until completion in all cases.MyTask-EnsureCompletion();delete Task;}
FQueuedThreadPool线程池 FQueuedThreadPool和一般的线程池实现类似线程池里面维护了多个线程FQueuedThread与多个任务队列IQueuedWork线程是按照队列的方式来排列的。在引擎PreInit的时候执行相关的初始化操作代码如下
// FEngineLoop.PreInit LaunchEngineLoop.cpp
if (FPlatformProcess::SupportsMultithreading())
{{GThreadPool FQueuedThreadPool::Allocate();int32 NumThreadsInThreadPool FPlatformMisc::NumberOfWorkerThreadsToSpawn();// we are only going to give dedicated servers one pool threadif (FPlatformProperties::IsServerOnly()){NumThreadsInThreadPool 1;}verify(GThreadPool-Create(NumThreadsInThreadPool, 128 * 1024));}
#ifUSE_NEW_ASYNC_IO{GIOThreadPool FQueuedThreadPool::Allocate();int32 NumThreadsInThreadPool FPlatformMisc::NumberOfIOWorkerThreadsToSpawn();if (FPlatformProperties::IsServerOnly()){NumThreadsInThreadPool 2;}verify(GIOThreadPool-Create(NumThreadsInThreadPool, 16 * 1024, TPri_AboveNormal));}
#endif// USE_NEW_ASYNC_IO#ifWITH_EDITOR// when we are in the editor we like to do things like build lighting and such// this thread pool can be used for those purposesGLargeThreadPool FQueuedThreadPool::Allocate();int32 NumThreadsInLargeThreadPool FMath::Max(FPlatformMisc::NumberOfCoresIncludingHyperthreads() - 2, 2);verify(GLargeThreadPool-Create(NumThreadsInLargeThreadPool, 128 * 1024));
#endif
} 专有服务器的线程池GThreadPool默认只开一个线程非专有服务器的根据核数开CoreNum-1个线程。编辑器模式会另外再创建一个线程池GLargeThreadPool包含LogicalCoreNum-2个线程用来处理贴图的压缩和编码相关内容。 在线程池里面所有的线程都是FQueuedThread类型不过更确切的说FQueuedThread是继承自FRunnable的线程执行体每个FQueuedThread里面包含一个FRunnableThread作为内部成员。 相比一般的线程FQueuedThread里面多了一个成员FEvent* DoWorkEvent也就是说FQueuedThread里面是有一个事件触发机制的。那么这个事件机制的作用是什么一般情况下来说就是在没有任务的时候挂起这个线程在添加并分配给该线程任务的时候激活他不过你可以灵活运用它在你需要的时候去动态控制线程任务的执行与暂停。前面我们在给线程池初始化的时候通过FQueuedThreadPool的Create函数创建了多个FQueuedThread然后每个FQueuedThread会执行Run函数里面有一段逻辑如下
//ThreadingBase.cpp
bool bContinueWaiting true;
while(bContinueWaiting )
{ DECLARE_SCOPE_CYCLE_COUNTER(TEXT( FQueuedThread::Run.WaitForWork ), STAT_FQueuedThread_Run_WaitForWork, STATGROUP_ThreadPoolAsyncTasks );// Wait for some work to dobContinueWaiting !DoWorkEvent-Wait( 10 );
}
//windows平台下的wait
bool FEventWin::Wait(uint32 WaitTime, const bool bIgnoreThreadIdleStats/* false*/)
{WaitForStats();SCOPE_CYCLE_COUNTER(STAT_EventWait );check(Event );FThreadIdleStats::FScopeIdleScope(bIgnoreThreadIdleStats );return (WaitForSingleObject( Event, WaitTime ) WAIT_OBJECT_0);
}我们看到当DoWorkEvent执行Wait的时候如果该线程的Event处于无信号状态默认刚创建是无信号的那么wait会等待10毫秒并返回false线程处于While无限循环中。如果线程池添加了任务AddQueuedWork并执行了DoWorkEvent的Trigger函数那么Event就会被设置为有信号Wait函数就会返回true随后线程跳出循环进而处理任务。
注FQueuedThread里的DoWorkEvent是通FPlatformProcess::GetSynchEventFromPool();从EventPool里面获取的。WaitForSingleObject等内容涉及到Windows下的事件机制大家可以自行到网上搜索相关的使用这里给出一个官方的 使用案例。 Asyntask与IQueuedWork 线程池的任务IQueuedWork本身是一个接口所以得有具体实现。这里你就应该能猜到所谓的AsynTask其实就是对IQueuedWork的具体实现。这里AsynTask泛指FAsyncTask与FAutoDeleteAsyncTask两个类我们先从FAsyncTask说起。
FAsyncTask有几个特点 FAsyncTask是一个模板类真正的AsyncTask需要你自己写。通过DoWork提供你要执行的具体任务然后把你的类作为模板参数传过去使用FAsyncTask就默认你要使用UE提供的线程池FQueuedThreadPool前面代码里说明了在引擎PreInit的时候会初始化线程池并返回一个指针GThreadPool。在执行FAsyncTask任务时如果你在执行StartBackgroundTask的时候会默认使用GThreadPool线程池当然你也可以在参数里面指定自己创建的线程池创建FAsyncTask并不一定要使用新的线程你可以调用函数StartSynchronousTask直接在当前线程上执行任务FAsyncTask本身包含一个DoneEvent任务执行完成的时候会激活该事件。当你想等待一个任务完成时再做其他操作就可以调用EnsureCompletion函数他可以从队列里面取出来还没被执行的任务放到当前线程来做也可以挂起当前线程等待DoneEvent激活后再往下执行 FAutoDeleteAsyncTask与FAsyncTask是相似的但是有一些差异 默认使用UE提供的线程池FQueuedThreadPool可以通过参数指定使用其他线程池FAutoDeleteAsyncTask在任务完成后会通过线程池的Destroy函数删除自身或者在执行DoWork后删除自身而FAsyncTask需要手动delete包含FAsyncTask的特点1和特点3 总的来说AsyncTask系统实现的多线程与你自己字节继承FRunnable实现的原理相似不过他在用法上比较简单而且还可以直接借用UE4提供的线程池很方便。
线程同步 当多个线程共享相同的内存时需要确保每个线程看到一致的数据视图。如果每个线程使用的变量都是其他线程不会读取或者修改的那么就不存在一致性问题。同样地如果变量是只读的多个线程同时读取该变量也不会有一致性问题。但是当某个线程可以修改变量而其他线程也可以读取或者修改这个变量的时候就需要对这些线程进行同步以确保它们在访问变量的存储内容时不会访问到无效的数值。这个时候就需要用到线程同步机制。
UE4提供了以下几个不同类别的同步机制
Atomics 原子机制 Atomic operations(原子操作) 保证CPU在读取和写入内存时总线操作是不可分割的。它是许多高级同步机制的基础主要优势是可以进行比较快的进行比较和解锁操作。一个用Atomics实现的样例如下
class FThreadSafeCounter{
public:
int32 Add( int32 Amount ) {return FPlatformAtomics::InterlockedAdd(Counter, Amount);}
private:volatile int32 Counter; // 因为值可能以编译器无法预测的异步方式被改变声明为volatile禁用优化
};Locking 锁机制 在UE4中常用的两种锁机制是 Critical Sections(临界区和 SpinLocks(自旋锁。
FSpinLock 自旋锁FScopeLock区域锁FCriticalSection 临界区FRWLock 读写锁 Signaling 信号机制
FSemaphore信号量与互斥锁类型但是他包含了一种信号机制。缺点是不是所有平台都支持。更加常用的线程间通信机制是 FEvent。 Waiting
FEvent事件 阻塞直至被触发或者超时经常被用来激活其他工作线程FScopedEvent区域事件 对FEvent的一次包装阻塞在域代码退出时
{FScopedEvent MyEvent;SendReferenceOrPointerToSomeOtherThread(MyEvent); // Other thread calls MyEvent-Trigger() ;// MyEvent destructor is here, we wait here.
}其中 FCriticalSection 是根据各个平台的互斥锁进行的抽象。Windows 平台是基于Windows平台的临界区。常用的iOS, Android,Linux平台则是使用的POSIX的线程标准实现[13]。
其他 UE4常见的容器类【TArray, TMap, TSet】通常都不是线程安全的需要我们仔细编写代码保证线程安全。下面是几个常见的线程安全类
FThreadSafeCounter计数器FThreadSingleton 单例类FThreadIdleStats 线程空闲状态统计类TLockFreePointerList 无锁队列TQueue队列 下面是一个简单的线程安全TSet附带FCriticalSection使用示例。
/** Simple thread safe proxy for TSetFName */
template typename T
class FThreadSafeSet
{TSetT InnerSet;FCriticalSection SetCritical;
public:void Add(T InValue) {FScopeLock SetLock(SetCritical);InnerSet.Add(InValue);}bool AddUnique(T InValue) {FScopeLock SetLock(SetCritical);if (!InnerSet.Contains(InValue)){InnerSet.Add(InValue);return true;}return false;}bool Contains(T InValue) {FScopeLock SetLock(SetCritical);return InnerSet.Contains(InValue);}void Remove(T InValue) {FScopeLock SetLock(SetCritical);InnerSet.Remove(InValue);}void Empty() {FScopeLock SetLock(SetCritical);InnerSet.Empty();}void GetValues(TSetT OutSet) {FScopeLock SetLock(SetCritical);OutSet.Append(InnerSet);}int32 Num() { return InnerSet.Num();}
};完整代码
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
#include BlankProgram.h
#include RequiredProgramMainCPPInclude.h
#include HAL/Runnable.h
#include HAL/RunnableThread.h
#include Async/AsyncWork.hDEFINE_LOG_CATEGORY_STATIC(LogBlankProgram, Log, All);
IMPLEMENT_APPLICATION(BlankProgram, BlankProgram);class MyRunnable : public FRunnable {
public:virtual bool Init() override; // 初始化 runnable 对象virtual uint32 Run() override; // 运行 runnable 对象virtual void Stop() override; // 停止 runnable 对象,线程提前终止时被调用virtual void Exit() override; // 退出 runnable 对象
};bool MyRunnable::Init() {UE_LOG(LogBlankProgram, Display, TEXT(Thread Init.));return true;
}uint32 MyRunnable::Run() {UE_LOG(LogBlankProgram, Display, TEXT(Thread Run.));return 0;
}void MyRunnable::Stop() {}void MyRunnable::Exit() {UE_LOG(LogBlankProgram, Display, TEXT(Thread Exit.));
}// 任务队列
class ExampleAsyncTask : public FNonAbandonableTask {friend class FAsyncTaskExampleAsyncTask;friend class FAutoDeleteAsyncTaskExampleAsyncTask;int32 ExampleData;ExampleAsyncTask(int32 InExampleData): ExampleData(InExampleData){}void DoWork() {UE_LOG(LogBlankProgram, Display, TEXT(ExampleAsyncTask %d Work.), ExampleData);}FORCEINLINE TStatId GetStatId() const {RETURN_QUICK_DECLARE_CYCLE_STAT(ExampleAsyncTask, STATGROUP_ThreadPoolAsyncTasks);}
};INT32_MAIN_INT32_ARGC_TCHAR_ARGV()
{GEngineLoop.PreInit(ArgC, ArgV);UE_LOG(LogBlankProgram, Display, TEXT(UE4 Multithreading Example.));// 1. FRunnable 使用示例FRunnable * Runnable new MyRunnable();FRunnableThread* RunnableThread FRunnableThread::Create(Runnable, TEXT(LaLaLaDeMaXiYa!));RunnableThread-WaitForCompletion();// 2.1 线程池异步队列FAsyncTaskExampleAsyncTask* MyTask new FAsyncTaskExampleAsyncTask(1);// 交由后台控制任务开始执行时机MyTask-StartBackgroundTask();// 确保线程被执行完成MyTask-EnsureCompletion();delete MyTask;// 2.2 线程池异步队列FAsyncTaskExampleAsyncTask* MyTask2 new FAsyncTaskExampleAsyncTask(2);// 直接在当前线程中执行MyTask2-StartSynchronousTask();// 检查任务是否完成if (MyTask2-IsDone()) {UE_LOG(LogBlankProgram, Display, TEXT(MyTask2 is Done.));}MyTask2-EnsureCompletion();delete MyTask2;// 2.3 带自动销毁的异步任务// 交由后台控制任务开始执行时机(new FAutoDeleteAsyncTaskExampleAsyncTask(3))-StartBackgroundTask();// 直接在当前线程中开始执行(new FAutoDeleteAsyncTaskExampleAsyncTask(4))-StartSynchronousTask();return 0;
}