Clang的线程安全分析静态工具
本文内容来自 Thread Safety Analysis,如需完整学习,请参考相关链接。
Clang线程安全分析工具是C++语言的一种扩展,用于警告代码中潜在的竞争条件。它在编译期间进行静态分析,无运行期性能损耗。即使该工具仍处在开发阶段,但已足够成熟,适合部署在生产环境上。
它的工作原理类似于一个针对多线程编程的类型系统。例如,变量 foo
可被多线程访问,当分析工具检测到该变量在读写时没有被对应的锁所保护时,会提示一个警告。
基本概念
clang的线程安全分析工具使用capabilities
来保护资源。这里的资源指的是成员数据,或者是提供访问底层资源的函数或方法。它能确保调用线程只有先拥有capability
,才能访问对应资源。
假如 mu
代表一个 mutex
,当线程执行 mu.Lock()
后,代表它获得了访问 mu
所保护资源的 capability
,当它调用 mu.unLock()
后,代表它释放了该 capability
。该线程拥有的capability
是不可拷贝的,也不能销毁它,它只能释放该capability
,以便其他线程获得该capability
。
使用方法
线程安全分析工具使用属性注解来声明依赖,这些属性注解是附加在类、方法、数据成员之上的。官方推荐使用宏(mutex.h)来使属性注解可读性更好、可理解。
运行方法很简单,执行
clang -c -Wthread-safety example.cpp
适用于变量的注解
-
GUARDED_BY: 该属性声明,线程在对该变量进行读写之前,一定要获得对应的锁,以确保对该变量的操作是线程安全的。
-
PT_GUARDED_BY: 和GUARDED_BY类似,它用于指针和智能指针上,对指针自身没有约束,但对它所指向的数据施加属性保护。
Mutex mu;
int *p1 GUARDED_BY(mu);
int *p2 PT_GUARDED_BY(mu);
unique_ptr<int> p3 PT_GUARDED_BY(mu);
void test() {
p1 = 0; // Warning!
*p2 = 42; // Warning!
p2 = new int; // OK.
*p3 = 42; // Warning!
p3.reset(new int); // OK.
}
适用于函数的注解
-
REQUIRES(mu) 指示调用线程在调用该函数前,必须先获得mu锁。它假设调用者在调用该函数前,已经拥有mu锁,内部对共享变量的修改无需额外加锁。
-
REQUIRES_SHARED(), 与 REQUIRES 类似,但仅要求共享读访问权限。
Mutex mu1, mu2;
int a GUARDED_BY(mu1);
int b GUARDED_BY(mu2);
void foo() REQUIRES(mu1, mu2) {
a = 0;
b = 0;
}
void test() {
mu1.Lock();
foo(); // Warning! Requires mu2.
mu1.Unlock();
}
- ACQUIRE() 声明要求函数内部获得锁,直到退出该函数也无需释放它。调用者在调用该函数前,不能持有该锁。
- RELEASE() 声明函数具备释放锁的能力,调用者在调用入口处无需持有该锁,函数退出前会主动释放该锁。
Mutex mu;
MyClass myObject GUARDED_BY(mu);
void lockAndInit() ACQUIRE(mu) {
mu.Lock();
myObject.init();
}
void cleanupAndUnlock() RELEASE(mu) {
myObject.cleanup();
} // Warning! Need to unlock mu.
void test() {
lockAndInit();
myObject.doSomething();
cleanupAndUnlock();
myObject.doSomething(); // Warning, mu is not locked.
}
如果没有参数传递给 ACQUIRE 或 RELEASE,则假定入参为 this ,分析工具不会检查函数内部实现。常用在隐藏抽象接口的内部锁的实现细节。
- EXCLUDES(...) 声明调用者不能持有给定
capabilities
, 该注解用来预防死锁,对于不可重入的锁,当同一个函数重入时,会获得2次锁,造成死锁。
Mutex mu;
int a GUARDED_BY(mu);
void clear() EXCLUDES(mu) {
mu.Lock();
a = 0;
mu.Unlock();
}
void reset() {
mu.Lock();
clear(); // Warning! Caller cannot hold 'mu'.
mu.Unlock();
}
- NO_THREAD_SAFETY_ANALYSIS: 关闭线程安全检查,该属性不属于声明的一部分,使用时要放在 .cpp 文件中。
适用于类的注解
-
CAPABILITY(
): 指明该类的实例可被用作 capaility
,string参数用来表明该capaility
的种类,在警告时会输出。 -
SCOPED_CAPABILITY:指明该类用于RAII风格的资源管理。