概述

  • UE4里,提供的多线程的方法:

    • 继承 FRunnable 接口创建单个线程
    • 创建 AsyncTask 调用线程池里面空闲的线程
    • 通过 TaskGraph 系统来异步完成一些自定义任务
    • 支持原生的多线程 std::thread
  • 在GameThread线程之外的其他线程中
    • 不要 spawning / modifying / deleting UObjects / AActors
    • 不要使用定时器 TimerManager
    • 不要使用任何绘制接口,例如 DrawDebugLine,然有可能崩溃
    • 如果想做的话,可以在主线程中异步处理
    • 其他线程中一般做数据收发和解析,数学运算等

本文主要介绍 FRunnable 类

FRunnable

  • FRunnable是UE4中多线程的实现方式之一,适用于复杂运算

  • FRunnable 是线程的执行体,提供相应的接口。FRunnable需要依附与一个FRunnableThread对象,才能被执行

    class CORE_API FRunnable
    {
    public:
    // ....
    virtual bool Init(); // 初始化 runnable 对象,在FRunnableThread创建线程对象后调用 virtual uint32 Run() = 0; // Runnable 对象逻辑处理主体,在Init成功后调用 virtual void Stop() {} // 停止 runnable 对象, 线程提前终止时被用户调用 virtual void Exit() {} // 退出 runnable 对象,由FRunnableThread调用
    };
  • FRunnableThread 表示一个可执行的线程,该类会派生出平台相关的子类。通过调用 FRunnableThread::Create 完成线程的创建

快速创建一个线程

创建 FRunnable 派生类

// .h
class TIPS_API FSimpleRunnable: public FRunnable
{
public:
FSimpleRunnable(const FString& ThreadName);
~FSimpleRunnable();
void PauseThread(); // 线程挂起 方法一
void WakeUpThread(); // 线程唤醒 方法一
void Suspend(bool bSuspend); // 线程挂起/唤醒 方法二
void StopThread(); // 停止线程,一般用该方法
void ShutDown(bool bShouldWait);// 停止线程,bShouldWait true的时候可强制 kill 线程 private:
FString m_ThreadName;
int32 m_ThreadID;
bool bRun = true; // 线程循环标志
bool bPause = false; //线程挂起标志
FRunnableThread* ThreadIns; // 线程实例
FEvent* ThreadEvent; //FEvent指针,挂起/激活线程, 在各自的线程内使用 virtual bool Init() override;
virtual uint32 Run() override;
virtual void Stop() override;
virtual void Exit() override;
};
// .cpp
FSimpleRunnable::FSimpleRunnable(const FString& ThreadName)
{
// 获取 FEvent 指针
ThreadEvent = FPlatformProcess::GetSynchEventFromPool(); // 创建线程实例
m_ThreadName = ThreadName;
ThreadIns = FRunnableThread::Create(this, *m_ThreadName, 0, TPri_Normal);
m_ThreadID = ThreadIns->GetThreadID();
UE_LOG(LogTemp, Warning, TEXT("Thread Start! ThreadID = %d"), m_ThreadID);
} FSimpleRunnable::~FSimpleRunnable()
{
if (ThreadEvent) // 清空 FEvent*
{
FPlatformProcess::ReturnSynchEventToPool(ThreadEvent); // delete ThreadEvent;
ThreadEvent = nullptr;
}
if (ThreadIns) // 清空 FRunnableThread*
{
delete ThreadIns;
ThreadIns = nullptr;
}
} bool FSimpleRunnable::Init()
{
return true; //若返回 false ,线程创建失败,不会执行后续函数
} uint32 FSimpleRunnable::Run()
{
int32 count = 0;
FPlatformProcess::Sleep(0.03f); //延时,等待初始化完成
while (bRun)
{
if (bPause)
{
ThreadEvent->Wait(); // 线程挂起
if (!bRun) // 线程挂起时执行线程结束
{
return 0;
}
} UE_LOG(LogTemp, Warning, TEXT("ThreadID: %d, Count: %d"),m_ThreadID, count);
count++;
FPlatformProcess::Sleep(0.1f); // 执行间隔,防止堵塞
}
return 0;
} void FSimpleRunnable::Stop()
{
bRun = false;
bPause = false;
if (ThreadEvent)
{
ThreadEvent->Trigger(); // 保证线程不挂起
}
Suspend(false); // 保证线程不挂起,本例只是为了暂时不同的挂起方法,如果不使用Suspend(),无需使用
} void FSimpleRunnable::Exit()
{
UE_LOG(LogTemp, Warning, TEXT("Thread Exit!"));
} void FSimpleRunnable::PauseThread()
{
bPause = true;
UE_LOG(LogTemp, Warning, TEXT("Thread Pause!"));
} void FSimpleRunnable::WakeUpThread()
{
bPause = false;
if (ThreadEvent)
{
ThreadEvent->Trigger(); // 唤醒线程
}
UE_LOG(LogTemp, Warning, TEXT("Thread Wakeup!"));
} void FSimpleRunnable::Suspend(bool bSuspend)
{
if (ThreadIns)
{
ThreadIns->Suspend(bSuspend); //挂起/唤醒
}
} void FSimpleRunnable::StopThread()
{
Stop();
ThreadIns->WaitForCompletion(); // 等待线程执行完毕
} void FSimpleRunnable::ShutDown(bool bShouldWait)
{
if (ThreadIns)
{
ThreadIns->Kill(bShouldWait); // bShouldWait 为false,Suspend(true)时,会崩
}
}

创建调用多线程的Actor

// .h
protected:
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; private:
FSimpleRunnable* SimpleRunnable; public:
UFUNCTION(BlueprintCallable)
void CreateNewThread(const FString& ThreadName); UFUNCTION(BlueprintCallable)
void PauseThread(); UFUNCTION(BlueprintCallable)
void SuspendThread(bool bSuspend); UFUNCTION(BlueprintCallable)
void WakeUpThread(); UFUNCTION(BlueprintCallable)
void StopThread(); UFUNCTION(BlueprintCallable)
void ForceKillThread(bool bShouldWait);
};
// .cpp
void ARunnableActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
if (SimpleRunnable) // 防止线程挂起,退出无响应
{
SimpleRunnable->StopThread();
delete SimpleRunnable;
SimpleRunnable = nullptr;
}
} void ARunnableActor::CreateNewThread(const FString& ThreadName)
{
SimpleRunnable = new FSimpleRunnable(ThreadName);
} void ARunnableActor::PauseThread()
{
if (SimpleRunnable)
{
SimpleRunnable->PauseThread();
}
} void ARunnableActor::SuspendThread(bool bSuspend)
{
if (SimpleRunnable)
{
SimpleRunnable->Suspend(bSuspend);
}
} void ARunnableActor::WakeUpThread()
{
if (SimpleRunnable)
{
SimpleRunnable->WakeUpThread();
}
} void ARunnableActor::StopThread()
{
if (SimpleRunnable)
{
SimpleRunnable->StopThread();
}
} void ARunnableActor::ForceKillThread(bool bShouldWait)
{
if (SimpleRunnable)
{
SimpleRunnable->ShutDown(bShouldWait);
delete SimpleRunnable;
SimpleRunnable = nullptr;
}
}

单例线程

  • 当希望线程只能创建一次时,可以通过声明静态单例FRunnable (本例为FSimpleRunnable)
// .h
static FSimpleRunnable* MySimpleRunnable; // 声明静态单例
static FSimpleRunnable* JoyInit(); // 声明静态方法 // cpp
// 初始化静态单例
FSimpleRunnable* FSimpleRunnable::MySimpleRunnable = nullptr;
//创建 SimpleRunnable 实例
FSimpleRunnable* FSimpleRunnable::JoyInit()
{
if (!MySimpleRunnable && FPlatformProcess::SupportsMultithreading())
{
MySimpleRunnable = new FSimpleRunnable();
}
return MySimpleRunnable;
}

多个线程

当希望执行多个线程时

  • 可用TMap<Name, FRunnable > 存储,移除
  • 也可设定线程结束条件,让其自行结束线程

线程锁

UE4 线程锁包括:

  • FSpinLock 自旋锁
  • FScopeLock 区域锁
  • FCriticalSection 临界区
  • FRWLock 读写锁

本文使用 FScopeLock 、FCriticalSection 作为测试

不使用线程锁

本例使用两个线程 为同一个整数做加法,知道该整数到达目标值

  • 修改 SimpleRunnable 代码

    FSimpleRunnable(const FString& ThreadName, int32* CurrentNumber, int32 MaxNumber);
    
    int32* m_CurrentNumber;
    int32 m_MaxNumber;
    int32 m_CalcCount = 0;
    FSimpleRunnable::FSimpleRunnable(const FString& ThreadName, int32* CurrentNumber, int32 MaxNumber)
    {
    /* 省略部分代码 */
    m_CurrentNumber = CurrentNumber;
    m_MaxNumber = MaxNumber;
    /* 省略部分代码 */
    } uint32 FSimpleRunnable::Run()
    { FPlatformProcess::Sleep(0.03f); //延时,等待初始化完成
    while (bRun && *m_CurrentNumber<m_MaxNumber)
    {
    /* 省略部分代码 */
    (*m_CurrentNumber)++;
    m_CalcCount++;
    if (m_CalcCount % 100 == 0)
    {
    UE_LOG(LogTemp, Warning, TEXT("ThreadID: %d, CurrentNumber: %d"),m_ThreadID, *m_CurrentNumber);
    }
    FPlatformProcess::Sleep(0.0001f); // 执行间隔,防止堵塞
    }
    return 0;
    } void FSimpleRunnable::Exit()
    {
    UE_LOG(LogTemp, Warning, TEXT("Thread Exit! ThreadID: %d, CurrentNumber: %d, CalcCount: %d"),m_ThreadID, *m_CurrentNumber, m_CalcCount);
    }
  • 修改 RunnableActor 代码

    UPROPERTY(EditAnywhere)
    int32 m_MaxNumber = 1000;
    void ARunnableActor::CreateNewThread(const FString& ThreadName)
    {
    SimpleRunnable = new FSimpleRunnable(TEXT("Thread1"), &m_CurrentNumber, m_MaxNumber);
    SimpleRunnable = new FSimpleRunnable(TEXT("Thread2"), &m_CurrentNumber, m_MaxNumber);
    }

使用线程锁

  • 注意 FCriticalSection 是否使用 static 声明

FScopeLock

  • 方法一

    修改 SimpleRunnable 代码

    { // 注意这个作用域用于 **FScopeLock** 
    static FCriticalSection m_mutex; //声明 staic 可以让线程之间互锁
    FScopeLock ScopeLock(&m_mutex); // 该作用域内上锁 (*m_CurrentNumber)++;
    }
    m_CalcCount++;
  • 方法二

    修改 SimpleRunnable 代码

    static FCriticalSection m_mutex; //声明 staic 可以让线程之间互锁
    FScopeLock* ScopeLock = new FScopeLock(&m_mutex); // 上锁 (*m_CurrentNumber)++; delete ScopeLock; // 解锁

FCriticalSection Lock()/UnLock()

修改 SimpleRunnable 代码

// 放在类声明static ,使用 Lock() 编译不通过
// static 可以让线程之间互锁,不使用 static 锁不生效
// 不使用 static,线程内可以上锁。可以在类中声明
static FCriticalSection m_mutex; m_mutex.Lock(); // 上锁 (*m_CurrentNumber)++; m_mutex.Unlock(); // 解锁

参考

【UE4 C++ 基础知识】<12> 多线程——FRunnable的更多相关文章

  1. 【UE4 C++ 基础知识】<11>资源的同步加载与异步加载

    同步加载 同步加载会造成进程阻塞. FObjectFinder / FClassFinder 在构造函数加载 ConstructorHelpers::FObjectFinder Constructor ...

  2. 【UE4 C++ 基础知识】<3> 基本数据类型、字符串处理及转换

    基本数据类型 TCHAR TCHAR就是UE4通过对char和wchar_t的封装 char ANSI编码 wchar_t 宽字符的Unicode编码 使用 TEXT() 宏包裹作为字面值 TCHAR ...

  3. 【UE4 C++ 基础知识】<14> 多线程——AsyncTask

    概念 AsyncTask AsyncTask 系统是一套基于线程池的异步任务处理系统.每创建一个AsyncTas,都会被加入到线程池中进行执行 AsyncTask 泛指 FAsyncTask 和 FA ...

  4. 【UE4 C++ 基础知识】<13> 多线程——TaskGraph

    概述 TaskGraph 系统是UE4一套抽象的异步任务处理系统 TaskGraph 可以看作一种"基于任务的并行编程"设计思想下的实现 通过TaskGraph ,可以创建任意多线 ...

  5. java基础知识总结--多线程

    1.扩展Java.lang.Thread类 1.1.进程和线程的区别: 进程:每个进程都有自己独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1~n个线程. 线程:同一类线 ...

  6. JAVA基础知识之多线程——三种实现多线程的方法及区别

    所有JAVA线程都必须是Thread或其子类的实例. 继承Thread类创建线程 步骤如下, 定义Thead子类并实现run()方法,run()是线程执行体 创建此子类实例对象,即创建了线程对象 调用 ...

  7. 【UE4 C++ 基础知识】<8> Delegate 委托

    概念 定义 UE4中的delegate(委托)常用于解耦不同对象之间的关联:委托的触发者不与监听者有直接关联,两者通过委托对象间接地建立联系. 监听者通过将响应函数绑定到委托上,使得委托触发时立即收到 ...

  8. JAVA基础知识之多线程——线程组和未处理异常

    线程组 Java中的ThreadGroup类表示线程组,在创建新线程时,可以通过构造函数Thread(group...)来指定线程组. 线程组具有以下特征 如果没有显式指定线程组,则新线程属于默认线程 ...

  9. JAVA基础知识之多线程——线程通信

    传统的线程通信 Object提供了三个方法wait(), notify(), notifyAll()在线程之间进行通信,以此来解决线程间执行顺序等问题. wait():释放当前线程的同步监视控制器,并 ...

随机推荐

  1. MySQL——日志管理

    一.MySQL日志类型 1.错误:--log--error ---------------------*** host_name.err 2.常规: --general_log host_name.l ...

  2. github搜索技巧小结

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  3. vue-过滤器(filter)的使用详解

    前言 Vue 允许我们在项目中定义过滤器对我们页面的文本展示进行格式的控制,本文就来总结一下过滤器在项目中的常见使用方法. 正文 1.局部过滤器的注册 (1)无参局部过滤器 <div id=&q ...

  4. Redis-初见

    目录 启动and连接 JRedis 宝塔 Redis.conf RDB AOF(Append Only File) 发布和订阅 主从复制 一主二从 复制原理 宕机后的手动配置主机 哨兵模式 Redis ...

  5. 云真机兼容性自动化工具测试解决方案_www.alltesting.cn

    问题和背景 不同类型的品牌和硬件环境.不同版本的android操作系统.IO操作系统,以及不同的分辨率,造成相同的APP在不同的设备可能存在缺陷. 兼容性测试,就是让APP.小程序.H5程序,在所有的 ...

  6. linux traceroute追踪路由路径

    TraceRoute的工作原理 1.TraceRoute的工作原理:      traceroute 有使用两种:使用ICMP的和使用UDP的.Microsoft      使用ICMP,所以win9 ...

  7. java版gRPC实战之五:双向流

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  8. vue-cli3项目中使用vue-ueditor-wrap

    Vue + UEditor + v-model 双向绑定 一.安装 1 npm i vue-ueditor-wrap 2 # 或者 3 yarn add vue-ueditor-wrap 二.下载文件 ...

  9. 做一个U盘的学习路线

    最近想研究一个U盘,然后顺便熟悉一下USB协议.因为USB协议比较复杂, 常用的复杂外设除了WiFi,Ethernet,SDIO和USB这些就是USB了,学习USB的时候肯定要拿一个东西下手,所以简单 ...

  10. DEM数据全国各省的裁剪与分享(30m、90m、250m、1000m)

    1.简介: 数字高程模型(Digital Elevation Model),简称DEM,是通过有限的地形高程数据实现对地面地形的数字化模拟. 这次分享的数据是全国34个省份的DEM裁剪数据,一共有6期 ...