Skip to content

Latest commit

 

History

History
475 lines (353 loc) · 11 KB

File metadata and controls

475 lines (353 loc) · 11 KB

Thread-Sentry 使用指南

Thread-Sentry 提供三种数据竞态检测方式,从简单到精确,满足不同场景需求。

快速开始

1. 初始化

use thread_sentry::{init, report_issues};

fn main() {
    init();  // 初始化 Thread-Sentry
    
    // 你的代码...
    
    report_issues();  // 打印检测报告
}

三种使用方式

方式 1:Guard 自动检测(最简单)

适用场景:替换标准库 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 内部数据的竞态
  • ⚠️ 无法区分字段级别的竞态

方式 2:SentryField 字段跟踪(推荐)

适用场景:精确跟踪结构体字段访问

手动包装字段

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()

方式 3:手动注册 API(unsafe 代码)

适用场景: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 使用以下规则判断竞态:

规则 1:同线程安全

if a1.thread_id == a2.thread_id { return false; }
// 同一线程的访问是顺序执行的,不存在竞态

规则 2:双读安全

if a1.access_type == Read && a2.access_type == Read { return false; }
// 多线程同时读是安全的

规则 3:同锁保护安全

if a1.lock_held == a2.lock_held && a1.lock_held.is_some() { return false; }
// 同一个 Mutex/RwLock 保护的数据,访问是互斥的

规则 4:其他情况为竞态

// - 不同线程
// - 至少一个写操作
// - 无共同锁保护
return true;  // 检测到竞态

实际检测效果

场景 1:同锁保护(无竞态)

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

场景 2:不同锁保护(竞态)

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 代码 需手动 完全控制

常见问题

Q1: SentryField 如何自动获取 lock_id?

A: TLS(Thread-Local Storage)方案:

  • SentinelMutexGuard 创建时自动设置 TLS
  • SentryField 访问时自动从 TLS 读取
  • 完全自动,零干预

Q2: 同锁保护会误报吗?

A: 不会。检测规则明确:

  • lock_id 相同 → 同锁保护 → 无竞态
  • lock_id 不同 → 不同锁 → 检测竞态

Q3: 性能开销有多大?

A:

  • Guard 自动:< 5% 总体开销
  • SentryField:< 10% 总体开销
  • TLS 读取:< 1ns 单次开销

Q4: 能检测所有竞态吗?

A: 不能。限制:

  • 只能检测通过 Thread-Sentry API 的访问
  • unsafe 代码需手动注册
  • 未标记的数据无法检测

示例代码

完整示例见:

  • examples/demo.rs - 基础用法
  • examples/tls_demo.rs - TLS 自动跟踪
  • examples/real_world.rs - 实际场景

总结

推荐使用顺序

  1. 快速迁移:替换 Mutexthread_sentry::Mutex(方式 1)
  2. 精确跟踪:关键字段用 SentryField 包装(方式 2)
  3. unsafe 区域:手动注册关键地址(方式 3)

核心优势

  • ✅ 自动检测 + 自动打印
  • ✅ TLS 方案零干预
  • ✅ 同锁无误报
  • ✅ 生产可用(< 10% 开销)

现在你只需要

  1. init() - 初始化
  2. 替换类型或包装字段
  3. report_issues() - 查看报告

竞态会自动检测并打印!