一个通用的线程阻塞检测和定位工具,通过信号机制和堆栈捕获技术,帮助开发者快速定位线程阻塞问题。
- ✅ 主动检测阻塞:监控任务队列长度和执行速度,主动检测线程阻塞
- ✅ 堆栈捕获:使用信号机制强制中断线程,捕获阻塞点的完整堆栈信息
- ✅ 符号解析:自动解析函数名和源码位置(支持 C++ demangle)
- ✅ 数据持久化:使用 mmap 保存堆栈信息,确保数据不丢失
- ✅ 备用栈保护:使用备用栈执行信号处理,完整保留原始堆栈
- ✅ 跨平台支持:支持 Linux、Android 等平台
- CMake 3.15+
- C++17 编译器
- Linux 系统(支持 pthread、dl、mmap)
mkdir build
cd build
cmake ..
make编译完成后,可执行文件位于 build/blocking_detector_demo。
- 实现 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_;
}
};- 创建并配置 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);- 初始化和启动
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示例程序会演示:
- 正常任务执行(不会触发阻塞检测)
- 快速提交大量任务(会触发阻塞检测)
- 单个长时间阻塞任务
工具通过监控两个关键指标来检测阻塞:
- 任务队列长度:如果待执行任务数量超过阈值,说明任务积压
- 任务执行速度:如果上一个任务超过一定时间未完成,且队列中有任务等待,说明线程被阻塞
检测到阻塞
↓
发送 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]
...
==================================================================
- 编译选项:必须开启
-funwind-tables或-fexceptions才能进行堆栈回溯 - 信号安全:信号处理函数中只能调用异步信号安全的函数
- 线程 ID:确保
GetTargetThreadId()返回正确的线程 ID - 权限:确保有权限在
crash_dir目录下创建文件
A: 可能的原因:
- 编译时未开启
-funwind-tables或-fexceptions - 遇到没有调试信息的库
- 堆栈深度超过限制
解决方案:
- 确保 CMakeLists.txt 中设置了正确的编译选项
- 使用
-g选项编译以包含调试信息
A: 可以使用 addr2line 工具:
addr2line -e <可执行文件> -f -C -p <地址>例如:
addr2line -e ./myapp -f -C -p 0x12345678A:
- 根据系统负载调整阈值
- 增加确认机制(如连续多次检测到异常才触发)
- 记录历史数据,分析模式
本项目采用与 yasrtc 相同的许可证。
详细的技术文档请参考:线程阻塞检测工具开发指南.md