线程安全分析宏
概述
简介
提供基于 clang 的编译期线程安全分析(Thread Safety Analysis,TSA)注解宏。这些宏用于标注互斥锁、受保护数据、函数的前置/后置条件等,帮助编译器在编译阶段发现数据竞争和锁使用错误。在非 clang 编译器下,这些宏会被展开为空操作,以保持兼容性。
#include "thread_safety_analysis_macros.h"
涉及功能
能力(Capability)类宏
用于标注锁或可锁对象。
| 宏 | 说明 |
|---|---|
| CAPABILITY(x) | 标注类/结构体为可锁能力(如互斥锁)。用法:class Foo CAPABILITY("mutex") { ... }; |
| REENTRANT_CAPABILITY | 标注可重入(允许递归加锁)的可锁类 |
| SCOPED_CAPABILITY | 标注 RAII 类(如锁守卫),在构造函数中获取能力,在析构函数中释放 |
数据保护宏
用于标注受锁保护的变量。
| 宏 | 说明 |
|---|---|
| GUARDED_BY(x) | 标注成员变量由某互斥锁保护,访问该变量时需持有该锁 |
| PT_GUARDED_BY(x) | 类似 GUARDED_BY,用于指针变量,表示指针所指向的数据由指定互斥锁保护 |
锁序宏
用于声明锁的获取顺序,避免死锁。
| 宏 | 说明 |
|---|---|
| ACQUIRED_BEFORE(...) | 声明该能力必须在其后列出的能力之前获取 |
| ACQUIRED_AFTER(...) | 声明该能力必须在其后列出的能力之后获取 |
函数前置条件宏
描述调用函数前需满足的锁状态。
| 宏 | 说明 |
|---|---|
| REQUIRES(...) | 调用前必须持有指定的互斥锁(独占锁) |
| REQUIRES_SHARED(...) | 调用前必须持有指定的共享锁(读锁) |
| EXCLUDES(...) | 调用时不得持有指定的互斥锁,避免在持锁时调用此类函数引发死锁 |
函数锁操作宏
描述函数内部对锁的获取与释放。
| 宏 | 说明 |
|---|---|
| ACQUIRE(...) | 函数会获取(加锁)指定的互斥锁(独占) |
| ACQUIRE_SHARED(...) | 函数会以共享模式获取(加读锁)指定的互斥锁 |
| RELEASE(...) | 函数会释放(解锁)指定的互斥锁(此前以独占模式持有) |
| RELEASE_SHARED(...) | 函数会释放(解锁)以共享模式持有的互斥锁 |
| RELEASE_GENERIC(...) | 函数会释放互斥锁(兼容独占或共享模式),用于通用解锁函数 |
| TRY_ACQUIRE(...) | 函数会尝试获取互斥锁,第一个参数为布尔值,表示返回 true 时表示成功 |
| TRY_ACQUIRE_SHARED(...) | 函数会尝试以共享模式获取互斥锁 |
断言与返回宏
用于断言和返回值标注。
| 宏 | 说明 |
|---|---|
| ASSERT_CAPABILITY(x) | 断言当前线程持有指定锁 |
| ASSERT_SHARED_CAPABILITY(x) | 断言当前线程以共享模式持有指定锁 |
| RETURN_CAPABILITY(x) | 标注函数返回其持有的锁的引用或指针(常用于返回 capability 的 getter) |
| NO_THREAD_SAFETY_ANALYSIS | 关闭对当前函数或代码块的线程安全分析,用于抑制误报或分析器无法推导的情况 |
使用示例
1. 定义带注解的互斥锁与 RAII 锁守卫
class CAPABILITY("mutex") Mutex {
public:
void Lock() ACQUIRE() { /* ... */ }
void Unlock() RELEASE() { /* ... */ }
void ReaderLock() ACQUIRE_SHARED() { /* ... */ }
void ReaderUnlock() RELEASE_SHARED() { /* ... */ }
bool TryLock() TRY_ACQUIRE(true) { /* ... */ }
};
class SCOPED_CAPABILITY MutexLocker {
public:
MutexLocker(Mutex *mu) ACQUIRE(mu) : mu_(mu) { mu->Lock(); }
~MutexLocker() RELEASE() { if (mu_) mu_->Unlock(); }
private:
Mutex *mu_;
};
2. 标注受保护数据与成员函数
class Counter {
public:
void Increment() REQUIRES(mu_) { ++value_; }
int Get() REQUIRES_SHARED(mu_) { return value_; }
Mutex &GetMutex() RETURN_CAPABILITY(mu_) { return mu_; }
private:
Mutex mu_;
int value_ GUARDED_BY(mu_);
};
3. 使用标准库锁
使用标准库
std::mutex, std::lock_guard,需要增加-D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS编译选项。
#include <mutex>
#include <vector>
std::mutex mut;
std::vector<int> data{0,1} GUARDED_BY(mut);
// access with capabilities, no warning.
{
std::lock_guard<std::mutex> lock(mut);
int a = data.at(0);
}
// access without capabilities, warning.
{
int b = data.at(0);
}
编译:clang++ test.cpp -Wthread-safety -stdlib=libc++ -D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS -I base/include/ -o a.out。注:-I base/include为示例路径,实际路径需要根据项目调整,OH构建系统中包含TSA头文件需要在组件BUILD.gn中增加:
external_deps += [ c_utils:utils ]
4. 测试用例
测试用例编写简介:
- 定义具有capability的互斥锁类:
// Use CAPABILITY macro to asign capability
class CAPABILITY("mutex") Mutex {
public:
// Declare methods a mutuable exclusive lock should have.
void Lock() ACQUIRE();
// Acquire/lock this mutex for read operations.
void ReaderLock() ACQUIRE_SHARED();
// Release/unlock an exclusive mutex.
void Unlock() RELEASE();
// Release/unlock a shared mutex.
void ReaderUnlock() RELEASE_SHARED();
// Generic unlock, can unlock exclusive and shared mutexes.
void GenericUnlock() RELEASE_GENERIC();
// Try to acquire the mutex. Returns true on success, and false on failure.
bool TryLock() TRY_ACQUIRE(true);
// Try to acquire the mutex for read operations.
bool ReaderTryLock() TRY_ACQUIRE_SHARED(true);
// Assert that this mutex is currently held by the calling thread.
void AssertHeld() ASSERT_CAPABILITY(this) {}
// Assert that this mutex is currently held for read operations.
void AssertReaderHeld() ASSERT_SHARED_CAPABILITY(this) {}
// For negative capabilities.
const Mutex& operator!() const;
bool IsLocked() const;
bool IsSharedLocked() const;
private:
bool locked_;
bool sharedLocked_;
};
- 使用
HWTEST_F测试框架写具体用例:
HWTEST_F(UtilsThreadSafetyAnalysisTest, MutexBasicOperations, TestSize.Level0)
{
Mutex mu;
EXPECT_FALSE(mu.IsLocked());
// Exclusive lock / unlock.
mu.Lock();
EXPECT_TRUE(mu.IsLocked());
EXPECT_FALSE(mu.IsSharedLocked());
mu.Unlock();
EXPECT_FALSE(mu.IsLocked());
// Shared lock / unlock.
mu.ReaderLock();
EXPECT_TRUE(mu.IsLocked());
EXPECT_TRUE(mu.IsSharedLocked());
mu.ReaderUnlock();
EXPECT_FALSE(mu.IsLocked());
// TryLock should fail when the mutex is already locked.
bool locked = mu.TryLock();
EXPECT_TRUE(locked);
EXPECT_TRUE(mu.IsLocked());
// The second TryLock is expected to fail; in that case we cannot assume
// the lock is held again, so we must not call GenericUnlock for it.
bool lockedAgain = mu.TryLock();
EXPECT_FALSE(lockedAgain);
if (locked) {
mu.GenericUnlock();
}
EXPECT_FALSE(mu.IsLocked());
// Try shared lock when currently unlocked.
bool sharedLocked = mu.ReaderTryLock();
EXPECT_TRUE(sharedLocked);
EXPECT_TRUE(mu.IsLocked());
EXPECT_TRUE(mu.IsSharedLocked());
if (sharedLocked) {
mu.GenericUnlock();
}
EXPECT_FALSE(mu.IsLocked());
}
TSA检测宏比较特殊:上述测试用例实质上测试了Mutex类的持锁、解锁功能,而TSA宏在编译期发挥作用,即用例编译器成功即发挥检测作用且代码正确使用TSA宏。
5. 编译与运行
- 使用 clang 编译时,需启用
-Wthread-safety才能进行线程安全分析,详见如何启用分析。
常见问题
详细信息参考LLVM官方文档。
-
应用于
std::mutex, std::lock_guard:需要应用于标准库互斥锁,需要添加编译选项宏-D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS,且使用libc++而非libstdc++。但注意:该编译选项宏会使能clang编译器对所有使用标注库互斥锁、lock_guard代码进行线程安全性检测,可能引入新的告警。 -
编译器支持:线程安全分析仅在 clang 下生效。在 GCC、MSVC 等编译器下,这些宏会展开为空,不会报错,但也不会进行静态检查。
-
如何启用分析:使用 clang 时需添加编译选项
-Wthread-safety(包含了-Wthread-safety-analysis, -Wthread-safety-attributes, -Wthread-safety-precise, -Wthread-safety-reference,也可单独开启此四个安全检测宏)或-Wthread-safety-pointer(检测受保护变量的指针、指向受保护数据的指针),否则注解不会触发诊断(OH构建系统已默认开启)。OH构建系统默认提升thread-safety warning为error,可以使用-Wno-error=thread-safety降级为warning。 -
NO_THREAD_SAFETY_ANALYSIS 的用法:当分析器产生误报,或某些复杂的锁逻辑无法被正确推导时,可对相应函数使用
NO_THREAD_SAFETY_ANALYSIS跳过分析。应谨慎使用,避免掩盖真实问题。 -
SCOPED_CAPABILITY 与 RAII:用于锁守卫等 RAII 类型时,构造函数应使用
ACQUIRE或ACQUIRE_SHARED标注加锁操作,析构函数使用RELEASE或RELEASE_GENERIC标注解锁操作。 -
REENTRANT_CAPABILITY支持情况:当前OpenHarmony构建工具链的clang版本还不支持
REENTRANT_CAPABILITY,请等待升级。