Thread-Sentry 提供三种数据竞态检测方式,从简单到精确,满足不同场景需求。
use thread_sentry::{init, report_issues};
fn main() {
init(); // 初始化 Thread-Sentry
// 你的代码...
report_issues(); // 打印检测报告
}适用场景:替换标准库 Mutex/RwLock
use thread_sentry::{Mutex, RwLock, init, report_issues};
use std::sync::Arc;
use std::thread;
fn main() {
init();
// 替换 std::sync::Mutex → thread_sentry::Mutex
let data = Arc::new(Mutex::new(0u64));
// Thread 1: 写
let data1 = Arc::clone(&data);
thread::spawn(move || {
let mut guard = data1.lock();
*guard = 100; // 自动检测竞态 + 自动打印
});
// Thread 2: 读
let data2 = Arc::clone(&data);
thread::spawn(move || {
let guard = data2.lock();
let value = *guard; // 自动检测竞态 + 自动打印
});
report_issues();
}自动检测原理:
- 每次
deref_mut()(写访问)自动调用RaceDetector::record_access() - 检测到竞态立即打印详细报告
- 无需用户干预
优点:
- ✅ 零学习成本(只需替换类型)
- ✅ 完全自动检测
- ✅ 适合快速迁移
缺点:
⚠️ 只能检测 Guard 内部数据的竞态⚠️ 无法区分字段级别的竞态
适用场景:精确跟踪结构体字段访问
use thread_sentry::{Mutex, SentryField, init, report_issues};
use std::sync::Arc;
use std::thread;
struct SharedData {
counter: SentryField<u64>, // 用 SentryField 包装
flag: SentryField<bool>, // 自动跟踪访问
}
impl SharedData {
fn new() -> Self {
Self {
counter: SentryField::new(0),
flag: SentryField::new(false),
}
}
}
fn main() {
init();
let data = Arc::new(Mutex::new(SharedData::new()));
// Thread 1: 写字段
let data1 = Arc::clone(&data);
thread::spawn(move || {
let mut guard = data1.lock();
guard.counter.set(100); // 自动检测 + 自动打印
});
// Thread 2: 读字段
let data2 = Arc::clone(&data);
thread::spawn(move || {
let guard = data2.lock();
let counter = *guard.counter.get(); // 自动检测 + 自动打印
});
report_issues();
}TLS 自动跟踪原理:
用户代码: guard.counter.set(100)
↓
SentinelMutexGuard 创建时:
TLS::set_current_lock(lock_id) ← Guard 自动设置
↓
SentryField::set() 被调用:
lock_id = TLS::get_current_lock() ← 自动读取
↓
RaceDetector::record_access(lock_id) ← 自动检测
↓
检测到竞态 → report_race() ← 自动打印
优点:
- ✅ 字段级别精确跟踪
- ✅ TLS 方案完全自动
- ✅ 同锁保护无误报
- ✅ 不同锁准确检测
缺点:
⚠️ 需要手动包装字段⚠️ API 稍显冗长(get()/set())
适用场景:unsafe 代码访问非 Mutex 保护的数据
use thread_sentry::{Mutex, RaceDetector, AccessType, init, report_issues};
use std::sync::Arc;
use std::thread;
struct UnsafeWrapper<T>(std::cell::UnsafeCell<T>);
unsafe impl<T: Send> Sync for UnsafeWrapper<T> {}
fn main() {
init();
// unsafe 数据(不在 Mutex 内)
let raw_data = Arc::new(UnsafeWrapper(std::cell::UnsafeCell::new(0u64)));
// 用两个不同的 Mutex(错误用法,但能检测)
let mutex1 = Arc::new(Mutex::new(()));
let mutex2 = Arc::new(Mutex::new(()));
// Thread 1: 写
let raw1 = Arc::clone(&raw_data);
let m1 = Arc::clone(&mutex1);
thread::spawn(move || {
let guard = m1.lock();
unsafe {
*raw1.0.get() = 100; // unsafe 写
}
// 手动注册访问
if let Some(report) = RaceDetector::record_access_manual(
raw1.0.get() as usize, // 地址
guard.thread_id, // 线程ID
AccessType::Write, // 访问类型
Some(guard.lock_id), // 持有的锁
8, // 数据大小(字节)
) {
thread_sentry::report_race(&report); // 打印竞态
}
});
// Thread 2: 读
let raw2 = Arc::clone(&raw_data);
let m2 = Arc::clone(&mutex2);
thread::spawn(move || {
let guard = m2.lock();
unsafe {
let _val = *raw2.0.get(); // unsafe 读
}
// 手动注册访问
if let Some(report) = RaceDetector::record_access_manual(
raw2.0.get() as usize,
guard.thread_id,
AccessType::Read,
Some(guard.lock_id),
8,
) {
thread_sentry::report_race(&report);
}
});
report_issues();
}何时需要手动注册:
- unsafe 代码访问裸指针
- 访问不在
Mutex内的数据 - 需要精确控制检测范围
优点:
- ✅ 完全控制检测范围
- ✅ 适合 unsafe 代码区域
- ✅ 可以检测任意地址
缺点:
⚠️ 需要手动调用 API⚠️ 需要计算地址和大小⚠️ 容易遗漏
Thread-Sentry 使用以下规则判断竞态:
if a1.thread_id == a2.thread_id { return false; }
// 同一线程的访问是顺序执行的,不存在竞态if a1.access_type == Read && a2.access_type == Read { return false; }
// 多线程同时读是安全的if a1.lock_held == a2.lock_held && a1.lock_held.is_some() { return false; }
// 同一个 Mutex/RwLock 保护的数据,访问是互斥的// - 不同线程
// - 至少一个写操作
// - 无共同锁保护
return true; // 检测到竞态let data = Arc::new(Mutex::new(SharedData::new()));
// Thread 1
let guard = data.lock(); // lock_id=1
guard.counter.set(100); // SentryField 自动获取 lock_id=1
// Thread 2
let guard = data.lock(); // lock_id=1(同一个 Mutex)
guard.counter.get(); // SentryField 自动获取 lock_id=1
// 结果:✓ No race(同锁保护)输出:
✓ No issues detected by Thread Sentry
let mutex1 = Arc::new(Mutex::new(()));
let mutex2 = Arc::new(Mutex::new(()));
let raw_data = Arc::new(UnsafeWrapper(...));
// Thread 1
let guard = mutex1.lock(); // lock_id=2
unsafe { *raw_data = 100; }
RaceDetector::record_access_manual(addr, ..., lock_id=2);
// Thread 2
let guard = mutex2.lock(); // lock_id=3(不同的 Mutex)
unsafe { read raw_data; }
RaceDetector::record_access_manual(addr, ..., lock_id=3);
// 结果:⚠️ RACE DETECTED输出:
╔══════════════════════════════════════════════════════════╗
║ ⚡ RACE CONDITION DETECTED ║
╚══════════════════════════════════════════════════════════╝
Memory Address: 0x00000144713abe30
Access 1: (Thread 1)
Type: Write
Lock Held: Some(2)
Backtrace:
1. BacktraceFrame { ip: 0x7ff7b68aac30, ... }
2. BacktraceFrame { ip: 0x7ff7b685a9c6, ... }
...
Access 2: (Thread 2)
Type: Read
Lock Held: Some(3)
Backtrace:
1. BacktraceFrame { ip: 0x7ff7b68aac30, ... }
2. BacktraceFrame { ip: 0x7ff7b685a9c6, ... }
...
⚠️ Thread Sentry detected 1 issue(s)
// Layer 1: 核心数据(Guard 自动检测)
let critical = Arc::new(Mutex::new(Counter::new()));
// Layer 2: 字段跟踪(SentryField)
struct Metrics {
request_count: SentryField<u64>, // 自动跟踪
error_rate: SentryField<f64>, // 自动跟踪
}
// Layer 3: unsafe 区域(手动注册)
unsafe {
*buffer_ptr = data;
RaceDetector::record_access_manual(addr, ...);
}开发阶段:
1. 替换所有 Mutex/RwLock → Thread-Sentry
2. 关键字段用 SentryField 包装
3. unsafe 代码手动注册
测试阶段:
1. 运行测试 → 自动检测竞态
2. 查看 report_issues() 输出
3. 修复竞态问题
生产阶段:
1. Thread-Sentry 持续监控
2. 低开销(< 10%)
3. 实时竞态报告
| 方式 | 单次访问开销 | 总体开销 | 适用场景 |
|---|---|---|---|
| Guard 自动 | ~10ns | < 5% | 所有 Mutex/RwLock |
| SentryField | ~15ns | < 10% | 关键字段 |
| 手动注册 | ~20ns | 可控 | unsafe 代码 |
对比 TSan:
- TSan:500-1500% slowdown
- Thread-Sentry:< 10% slowdown
- 生产可用
| 方式 | 自动化程度 | 适用场景 | 需要用户干预 | 检测精度 |
|---|---|---|---|---|
| Guard 自动 | ⭐⭐⭐⭐⭐ | 所有 Mutex | 无 | 整体数据 |
| SentryField | ⭐⭐⭐⭐⭐ | 结构体字段 | 无(TLS) | 字段级别 |
| 手动注册 | ⭐⭐ | unsafe 代码 | 需手动 | 完全控制 |
A: TLS(Thread-Local Storage)方案:
SentinelMutexGuard创建时自动设置 TLSSentryField访问时自动从 TLS 读取- 完全自动,零干预
A: 不会。检测规则明确:
lock_id相同 → 同锁保护 → 无竞态lock_id不同 → 不同锁 → 检测竞态
A:
- Guard 自动:< 5% 总体开销
- SentryField:< 10% 总体开销
- TLS 读取:< 1ns 单次开销
A: 不能。限制:
- 只能检测通过 Thread-Sentry API 的访问
- unsafe 代码需手动注册
- 未标记的数据无法检测
完整示例见:
examples/demo.rs- 基础用法examples/tls_demo.rs- TLS 自动跟踪examples/real_world.rs- 实际场景
推荐使用顺序:
- 快速迁移:替换
Mutex→thread_sentry::Mutex(方式 1) - 精确跟踪:关键字段用
SentryField包装(方式 2) - unsafe 区域:手动注册关键地址(方式 3)
核心优势:
- ✅ 自动检测 + 自动打印
- ✅ TLS 方案零干预
- ✅ 同锁无误报
- ✅ 生产可用(< 10% 开销)
现在你只需要:
init()- 初始化- 替换类型或包装字段
report_issues()- 查看报告
竞态会自动检测并打印!