Skip to content

wisecubeagain/ThreadBlockingDetector

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

线程阻塞检测工具

一个通用的线程阻塞检测和定位工具,通过信号机制和堆栈捕获技术,帮助开发者快速定位线程阻塞问题。

功能特性

  • 主动检测阻塞:监控任务队列长度和执行速度,主动检测线程阻塞
  • 堆栈捕获:使用信号机制强制中断线程,捕获阻塞点的完整堆栈信息
  • 符号解析:自动解析函数名和源码位置(支持 C++ demangle)
  • 数据持久化:使用 mmap 保存堆栈信息,确保数据不丢失
  • 备用栈保护:使用备用栈执行信号处理,完整保留原始堆栈
  • 跨平台支持:支持 Linux、Android 等平台

编译

依赖要求

  • CMake 3.15+
  • C++17 编译器
  • Linux 系统(支持 pthread、dl、mmap)

编译步骤

mkdir build
cd build
cmake ..
make

编译完成后,可执行文件位于 build/blocking_detector_demo

使用方法

基本用法

  1. 实现 TaskStatistics 接口
class MyExecutor : public blocking_detector::TaskStatistics {
public:
    int GetPostedTaskCount() const override {
        return posted_count_;
    }
    
    int GetExecutedTaskCount() const override {
        return executed_count_;
    }
    
    pthread_t GetTargetThreadId() const override {
        return worker_thread_id_;
    }
};
  1. 创建并配置 BlockingDetector
blocking_detector::BlockingDetectorConfig config;
config.max_wait_count = 10000;      // 最大等待任务数阈值
config.block_wait_count = 2000;     // 阻塞阈值
config.check_interval_ms = 1000;    // 检测间隔(毫秒)
config.crash_dir = "/tmp/blocking_detector";  // 堆栈信息保存目录
config.abort_on_blocking = true;    // 检测到阻塞后是否终止程序

MyExecutor executor;
blocking_detector::BlockingDetector detector(&executor, config);
  1. 初始化和启动
if (detector.Init()) {
    detector.Start();
}

配置参数说明

参数 说明 默认值
max_wait_count 最大等待任务数阈值,超过此值判定为异常 10000
block_wait_count 阻塞阈值,任务执行速度为0且待执行任务数超过此值判定为阻塞 2000
check_interval_ms 检测间隔(毫秒) 1000
stack_depth 堆栈捕获深度 64
crash_dir 堆栈信息保存目录 /tmp/blocking_detector
abort_on_blocking 检测到阻塞后是否终止程序 true

运行示例

./build/blocking_detector_demo

示例程序会演示:

  1. 正常任务执行(不会触发阻塞检测)
  2. 快速提交大量任务(会触发阻塞检测)
  3. 单个长时间阻塞任务

工作原理

阻塞检测机制

工具通过监控两个关键指标来检测阻塞:

  1. 任务队列长度:如果待执行任务数量超过阈值,说明任务积压
  2. 任务执行速度:如果上一个任务超过一定时间未完成,且队列中有任务等待,说明线程被阻塞

堆栈捕获流程

检测到阻塞
    ↓
发送 SIGUSR1 信号到目标线程
    ↓
信号处理函数(使用备用栈)
    ↓
堆栈回溯(_Unwind_Backtrace)
    ↓
符号解析(dladdr + demangle)
    ↓
过滤内核相关堆栈帧
    ↓
保存到文件(mmap)

关键技术

  • 信号机制:使用 pthread_kill 发送 SIGUSR1 信号中断目标线程
  • 备用栈:使用 sigaltstack 设置备用栈,保护原始堆栈不被覆盖
  • 堆栈回溯:使用 _Unwind_Backtrace 进行堆栈回溯(需要编译时开启 -funwind-tables
  • 符号解析:使用 dladdr 获取符号信息,abi::__cxa_demangle 解析 C++ 符号
  • 数据持久化:使用 mmap 写入文件,确保数据完整性

堆栈信息格式

保存的堆栈信息文件格式如下:

线程阻塞检测报告
时间: Wed Jan 15 10:30:45 2025
待执行任务数: 5234
已提交任务数: 10000
已执行任务数: 4766

========================>>> 线程阻塞堆栈信息 <<<========================
时间: 1705282245

#00 0x00007f8b12345678 <- libmyapp.so [BlockingFunction()+0x123]
#01 0x00007f8b12345690 <- libmyapp.so [TaskEntry()+0x45]
#02 0x00007f8b123456a8 <- libmyapp.so [EventLoop::Run()+0x67]
...
==================================================================

注意事项

  1. 编译选项:必须开启 -funwind-tables-fexceptions 才能进行堆栈回溯
  2. 信号安全:信号处理函数中只能调用异步信号安全的函数
  3. 线程 ID:确保 GetTargetThreadId() 返回正确的线程 ID
  4. 权限:确保有权限在 crash_dir 目录下创建文件

常见问题

Q: 为什么捕获不到堆栈?

A: 可能的原因:

  • 编译时未开启 -funwind-tables-fexceptions
  • 遇到没有调试信息的库
  • 堆栈深度超过限制

解决方案:

  • 确保 CMakeLists.txt 中设置了正确的编译选项
  • 使用 -g 选项编译以包含调试信息

Q: 如何解析堆栈信息中的地址?

A: 可以使用 addr2line 工具:

addr2line -e <可执行文件> -f -C -p <地址>

例如:

addr2line -e ./myapp -f -C -p 0x12345678

Q: 如何避免误报?

A:

  • 根据系统负载调整阈值
  • 增加确认机制(如连续多次检测到异常才触发)
  • 记录历史数据,分析模式

许可证

本项目采用与 yasrtc 相同的许可证。

参考文档

详细的技术文档请参考:线程阻塞检测工具开发指南.md

About

Yet Another Simple Thread Blocking Detector

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •