问题背景
有的时候会突然发现游戏进入了卡死状态,或者某个线程的任务一直没有处理完成,这个时候就需要考虑是不是线程出现了卡死,比如资源竞争、死循环等。通常情况下,我们可以提出一个监听线程来用心跳机制判断某个线程是否仍然存活,如果某个线程一段时间内都没有上报心跳,那么就可以认为它卡死了,这个时候可以打出堆栈或者直接让程序触发assert
。
机制简介
在虚幻引擎中有一个FThreadHeartBeat
的类,用于维护线程心跳相关机制。
主线程心跳:GameThreadHitchHeartBeat
。
在引擎代码的各处,已经处理好了FThreadHeartBeat::Get().HeartBeat()
的调用,在一些不需要检测的地方,也调用了暂停检测。
核心思路
通过创建一个FThreadHeartBeat
的线程,定时检测参与心跳的其他线程上一次心跳的时间,如果超时则执行输出日志等操作。
还需要记录下来上一次卡死(Hang)的时间,避免重复地检测上报。
如果需要获取目标线程的堆栈,可以使用FPlatformStackWalk::CaptureThreadStackBackTrace
来获取指定线程的当前堆栈。
特殊的,对于渲染的检测,直接进行Crash
。
如何使用
开启配置
核心功能只需要在DefaultEngine.ini
中配置上对应的检测时间,即可对主线程的死锁进行检测:
|
|
需要注意默认的宏控制为:
|
|
也即如果是Editor
环境下,不会开启,如果想要测试可以把&& !WITH_EDITORONLY_DATA
删除掉重新编译。
自定义线程
如果需要检测其他的线程,只需要在对应线程的Tick中调用FThreadHeartBeat::HeartBeat()
让心跳线程记录下来即可。
长时间任务
有的时候,我们需要明确地长时间执行一个任务,这个时候可以使用FSlowHeartBeatScope
类来构造一个锁,让检测线程知道目前处于一个明确地不需要检测的时间段。
他的原理就是在构造函数的时候,调用FThreadHeartBeat::SuspendHeartBeat
标记SuspendedCount++
,析构的时候调用FThreadHeartBeat::ResumeHeartBeat
标记--SuspendedCount
。
在检测的时候,只会处理SuspendedCount == 0
的线程。