UE4 多线程理解

目前对于UE4来说,要实现多线程有多种方式,主要分为一下几种:

  1. FRunnable&&FRunnableThread
  2. TaskGraph系统
  3. AsyncTask系统

一、FRunnable&&FRunnableThread

UE4遵循C++11,因此,我们可以使用std::thread实现多线程。然而,UE4为了能够实现简单跨平台,实现了自己的多线程机制,也即是FRunnable,类代码如下所示:

class CORE_API FRunnable
{
public:

	/**
	 * Initializes the runnable object.
	 *
	 * This method is called in the context of the thread object that aggregates this, not the
	 * thread that passes this runnable to a new thread.
	 *
	 * @return True if initialization was successful, false otherwise
	 * @see Run, Stop, Exit
	 */
	virtual bool Init()
	{
		return true;
	}

	/**
	 * Runs the runnable object.
	 *
	 * This is where all per object thread work is done. This is only called if the initialization was successful.
	 *
	 * @return The exit code of the runnable object
	 * @see Init, Stop, Exit
	 */
	virtual uint32 Run() = 0;

	/**
	 * Stops the runnable object.
	 *
	 * This is called if a thread is requested to terminate early.
	 * @see Init, Run, Exit
	 */
	virtual void Stop() { }

	/**
	 * Exits the runnable object.
	 *
	 * Called in the context of the aggregating thread to perform any cleanup.
	 * @see Init, Run, Stop
	 */
	virtual void Exit() { }

	/**
	 * Gets single thread interface pointer used for ticking this runnable when multi-threading is disabled.
	 * If the interface is not implemented, this runnable will not be ticked when FPlatformProcess::SupportsMultithreading() is false.
	 *
	 * @return Pointer to the single thread interface or nullptr if not implemented.
	 */
	virtual class FSingleThreadRunnable* GetSingleThreadInterface( )
	{
		return nullptr;
	}

	/** Virtual destructor */
	virtual ~FRunnable() { }
};

是的,源码很少,而且全是接口函数。那么问题来着,FRunable是和std::thread一样的线程吗?
答案当然是否,实际上,在多线程实现的时候,FRunable仅仅是参数,而真正的线程是FRunnableThread。下面关系类图:
FRunnable关系类图
基于此,不难理解,FRunnable&&FRunnableThread实现方式为:

  1. 用户继承FRunnable,重写Init(),Run()等相关函数。
  2. 利用FRunnableThread::Create()函数,创建“线程”。
  3. 根据平台API,调用不同的类实现( FPlatformProcess::CreateRunnableThread()),创建真正的线程

    具体使用方式

创建基类,继承FRunnable:

UENUM(BlueprintType)
enum class EThreadStatus :uint8
{
	New UMETA(DisplayName ="New"),
	Runnable UMETA(DisplayName = "Runnable"),
	Blocked UMETA(DisplayName = "Blocked"),
	Waiting UMETA(DisplayName = "Waiting"),
	TimedWaiting UMETA(DisplayName='TimedWaiting'),
	Terminated UMETA(DisplayName = 'Terminated')
};

UENUM(BlueprintType)
enum class EPriority :uint8
{
	TPri_Normal UMETA(DisplayName = "TPri_Normal"),
	TPri_AboveNormal UMETA(DisplayName="TPri_AboveNormal"),
	TPri_BelowNormal UMETA(DisplayName = "TPri_BelowNormal"),
	TPri_Highest UMETA(DisplayName = "TPri_Highest"),
	TPri_Lowest UMETA(DisplayName = "TPri_Lowest"),
	TPri_SlightlyBelowNormal UMETA(DisplayName = "TPri_SlightlyBelowNormal"),
	TPri_TimeCritical UMETA(DisplayName = "TPri_TimeCritical")
};
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class EASYTHREAD_API UThreadWorkerComponent:public UActorComponent, public FRunnable
{
	GENERATED_BODY()
public:
		UThreadWorkerComponent();
		~UThreadWorkerComponent();

public:
	FString ThreadName;

	FRunnableThread* Thread;

	FThreadSafeCounter StopTaskCounter;

	FCriticalSection Mutex;

	FEvent* ThreadSuspendedEvent;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "EasyThread|Thread")
		EThreadStatus Status;

public:

	bool Init();

	uint32 Run();

	void EndPlay(const EEndPlayReason::Type EndPlayReason);

	void EnsureCompletion();

	UFUNCTION(BlueprintCallable, Category = "EasyThread|Thread")
		void Stop();
	UFUNCTION(BlueprintCallable, Category = "EasyThread|Thread")
		void Exit();

	UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "EasyThread|Thread")
		bool IsFinished();
		virtual bool IsFinished_Implementation();

	UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "EasyThread|Thread")
		int32 ThreadBody();
		virtual int32 ThreadBody_Implementation();


	UFUNCTION(BlueprintCallable, Category = "EasyThread|Thread")
		EThreadStatus GetThreadStatus();

	UFUNCTION(BlueprintCallable, Category = "EasyThread|Thread")
		void Suspend();

	UFUNCTION(BlueprintCallable, Category = "EasyThread|Thread")
		void Resume();

	UFUNCTION(BlueprintCallable, Category = "EasyThread|Thread")
		void TaskSleep(float Seconds);

	UFUNCTION(BlueprintCallable, Category = "EasyThread|Thread")
		void CreateThread(const FString InThreadName, const int32 InStackSize, const EPriority InThreadPriority);

	UFUNCTION(BlueprintCallable, Category = "EasyThread|Thread")
		bool Lock();

	UFUNCTION(BlueprintCallable, Category = "EasyThread|Thread")
		void UnLock();

	FCriticalSection* GetCriticalSection();
};

代码说明:

  • 建议在继承FRunnable同时继承UObject,使用UE4的GC
  • 为了能在蓝图以及Actor中直接使用,因此创建同时继承了FRunnable以及UActorComponent
  • 为了在编辑器中运行调试,重载EndPlay() -----这也是继承UActorComponent的原因之一,否则在PIE中运行后停止,容易存在线程阻塞。也即是说不能仅仅在析构函数中停止线程,如果要在PIE模式下调试,就需要在EndPlay()中终止线程。
// Copyright 2018 Bruce, Inc. All Rights Reserved

#include "ThreadWorker.h"
#include "Runtime/Core/Public/HAL/RunnableThread.h"
#include "Runtime/Core/Public/Misc/ScopeLock.h"


UThreadWorkerComponent::UThreadWorkerComponent():Status(EThreadStatus::New),StopTaskCounter(0)
{
	ThreadSuspendedEvent = FPlatformProcess::GetSynchEventFromPool();
}

UThreadWorkerComponent::~UThreadWorkerComponent()
{
	if (Thread)
	{
		delete Thread;
		Thread = nullptr;
	}
	FPlatformProcess::ReturnSynchEventToPool(ThreadSuspendedEvent);
	ThreadSuspendedEvent = nullptr;
}

uint32 UThreadWorkerComponent::Run()
{
	Status = EThreadStatus::Runnable;
	do 
	{
		ThreadBody();
	} while (StopTaskCounter.GetValue() == 0 && !IsFinished());
	return 1;
}

void UThreadWorkerComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);
	//EnsureCompletion();
	if (Thread)
	{
		Thread->Kill(false);
		Stop();
	}
}

void UThreadWorkerComponent::EnsureCompletion()
{
	if (Thread)
	{
		Thread->Kill(true);
		Stop();
		Thread->WaitForCompletion();
	}
}

void UThreadWorkerComponent::Stop()
{
	if (Status != EThreadStatus::Terminated)
	{
		StopTaskCounter.Increment();
		UE_LOG(LogClass, Log, TEXT("---------------Stop---------------"));
		Status = EThreadStatus::Terminated;
	}
}

void UThreadWorkerComponent::Exit()
{
	if (Status != EThreadStatus::Terminated)
	{
		StopTaskCounter.Increment();
		Status = EThreadStatus::Terminated;
	}

}

bool UThreadWorkerComponent::IsFinished_Implementation()
{
	return true;
}

int32 UThreadWorkerComponent::ThreadBody_Implementation()
{
	return 0;
}

EThreadStatus UThreadWorkerComponent::GetThreadStatus()
{
	return Status;
}

void UThreadWorkerComponent::Suspend()
{
	ThreadSuspendedEvent->Wait();
	Status = EThreadStatus::Waiting;
}

void UThreadWorkerComponent::Resume()
{
	ThreadSuspendedEvent->Trigger();
	Status = EThreadStatus::Runnable;
}

void UThreadWorkerComponent::CreateThread(const FString InThreadName, const int32 InStackSize, const EPriority InThreadPriority)
{
	EThreadPriority ThreadPriority = static_cast<EThreadPriority>(static_cast<uint8>(InThreadPriority));
	ThreadName = InThreadName;
	Thread = FRunnableThread::Create(this, *ThreadName, InStackSize, ThreadPriority, FPlatformAffinity::GetNoAffinityMask());
}

bool UThreadWorkerComponent::Lock()
{
	return Mutex.TryLock();
}

void UThreadWorkerComponent::UnLock()
{
	Mutex.Unlock();
}

FCriticalSection* UThreadWorkerComponent::GetCriticalSection()
{
	return &Mutex;
}

bool UThreadWorkerComponent::Init()
{
	UE_LOG(LogClass, Log, TEXT("---------------%S---------------"), *ThreadName);
	Status = EThreadStatus::New;
	return true;
}

void UThreadWorkerComponent::TaskSleep(float Seconds)
{
	FPlatformProcess::Sleep(Seconds);
}

代码说明

  • 蓝图通过重写ThreadBody函数,以及IsFinished函数,之后调用CreateThread()便可创建线程。
  • 为了使用便利,重写之时可以不在ThreadBody中写循环,而是重写IsFinished函数
  • EndPlay()中,使用Kill函数终止线程,传入bShouldWait参数为否,立刻终止线程。此处存在两个问题,若为立即终止线程,若线程中存在死循环亦或阻塞函数,则线程永远不会停止(即使退出编辑器或者程序),该线程会卡死主线程,形成死锁。不过如果立即终止,若ThreadBody内为某个资源上锁,则会形成死锁,资源永远无法释放。在写这篇文章时,笔者暂用强制结束线程的方式,默认在Thread中不会锁住编辑器相关资源。
  • 其余函数实现参考WIKI及网上相关教程:
  • WIKI教程 By Rama

二、AsyncTask系统

待续

  • 7
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值